探索设计模式的魅力:工厂方法模式

工厂方法模式是一种创建型设计模式,它提供了一种创建对象的接口,但将具体实例化对象的工作推迟到子类中完成。这样做的目的是创建对象时不用依赖于具体的类,而是依赖于抽象,这提高了系统的灵活性和可扩展性。

以下是工厂方法模式的几个关键组成部分:

  1. 产品(Product): 定义了工厂方法所创建的对象的接口。在我们的日志记录器示例中,Logger 类就是一个产品接口。

  2. 具体产品(Concrete Product): 实现了产品接口的具体类。继承自Logger类的FileLoggerConsoleLogger 和 DatabaseLogger类就是具体产品。

  3. 创建者(Creator): 声明了工厂方法,这个方法返回一个产品类型的对象。通常情况下,创建者类将是抽象类,并包含工厂方法的声明。在我们的示例中,LoggerFactory就是一个创建者。

  4. 具体创建者(Concrete Creator): 覆盖了工厂方法以返回一个具体产品实例。这是实际决定要实例化哪一个产品的类。例如,FileLoggerFactoryConsoleLoggerFactory 和 DatabaseLoggerFactory都是具体创建者,它们覆盖工厂方法以返回它们各自的产品实例。

工厂方法模式的工作方式:

  • 定义产品接口: 首先定义一个产品接口,它描述了产品的公共接口。
  • 创建具体产品: 然后为每种类型的产品实现具体类。
  • 创建抽象创建者: 接着创建一个创建者(通常是抽象类或接口)来声明工厂方法。工厂方法通常会有一个返回类型为产品接口的返回类型。
  • 实现具体创建者: 创建具体创建者类来实现抽象创建者中声明的工厂方法,返回具体产品的实例。
  • 在应用中使用创建者: 最后在应用程序中,我们使用创建者类来调用工厂方法,获取产品对象的实例。

工厂方法模式的优点:

  • 降低耦合度: 客户代码从具体类解耦,并依赖于抽象。这意味着客户代码不需要改变就能与任何新增的具体产品工作。
  • 增加了系统的可扩展性: 新的具体产品可以很容易地加入到系统中,因为现有的客户代码不会受到影响。
  • 提高代码的可维护性: 如果一种产品在多处创建,更改产品的实现或者更换一个产品都会更加容易和集中。

工厂方法模式的缺点:

  • 增加了代码的复杂性: 可能需要引入许多新类,每种类型的产品都需要一个新的具体创建者类。
  • 需要更多的设计考虑: 设计好抽象创建者和具体创建者之间的关系需要一定的设计经验和考虑。

总的来说,工厂方法模式在需要灵活和可扩展的系统中非常有用,尤其是当我们预计产品类可能会经常改变时。它有助于保持一个健壮而灵活的代码库。

目录

一、案例

1.1 示例代码

1.2 扩展-添加数据库日志

二、模式讲解

2.1 功能

2.2 工厂方法模式结构

2.3 示例代码程序结构图

2.4 简单工厂方法结构图

2.5 工厂方法模式与简单工厂模式

三、扩展-工厂方法与IoC/DI

  3.1 弄明白IoC/DI

  3.2 工厂方法与IoC/DI的关系


一、案例

场景

        需要一个创建不同类型日志记录器的框架,日志记录器可能记录到文件、控制台或者数据库。

1.1 示例代码

        定义一个抽象日志记录器类和一个工厂方法:

/**
 * 日志处理抽象类
 */
public abstract class Logger {

    /**
     * 操作日志
     */
    public abstract void log(String message);
}

/**
 * 工厂方法抽象类
 */
public abstract class LoggerFactory {

    /**
     * 操作日志
     */
    public void log(String message) {
        createLogger().log(message);
    }
    /**
     * 工厂方法,创建日志对象的接口对象
     */
    public abstract Logger createLogger();
}

        为文件日志类型实现具体的日志记录器和对应的工厂:

/**
 * 文件日志实现类
 */
public class FileLogger extends Logger {
    public void log(String message) {
        // 逻辑来将消息写入文件
        System.out.println("File logger: " + message);
    }
}

/**
 * 文件日志工厂方法类
 */
public class FileLoggerFactory extends LoggerFactory {
    @Override
    public Logger createLogger() {
        // 可以在这里添加创建FileLogger所需的逻辑和初始化
        return new FileLogger();
    }
}

        为控制台日志类型实现具体的日志记录器和对应的工厂:

/**
 * 控制台日志实现类
 */
public class ConsoleLogger extends Logger {
    public void log(String message) {
        // 逻辑来将消息打印到控制台
        System.out.println("Console logger: " + message);
    }
}

/**
 * 控制台工厂方法类
 */
public class ConsoleLoggerFactory extends LoggerFactory {
    @Override
    public Logger createLogger() {
        // 创建ConsoleLogger的逻辑
        return new ConsoleLogger();
    }
}

        现在,在应用程序中,我们可以根据需要使用工厂来创建日志记录器对象,而不必直接实例化它们。这样,如果以后需要添加新的日志记录器类型(例如,数据库日志记录器),我们只需要添加一个新的工厂而不需要修改现有代码。

        客户端示例代码:

public class Application {
    public static void main(String[] args) {
        LoggerFactory factory = new FileLoggerFactory();
        // 或者 factory = new ConsoleLoggerFactory();
        factory.log("这是一条日志信息.");
    }
}

        当 Application 运行时,根据选择的工厂类型,它将使用对应的工厂创建一个日志记录器,并通过这个记录器记录消息。这个示例遵循了工厂方法的设计原则,因为它使对象的创建和使用分离,使得系统易于扩展和维护。

        

1.2 扩展-添加数据库日志

        如果我们想要将日志记录扩展到数据库,我们首先需要为数据库日志创建一个新的Logger子类,然后实现对应的工厂类。下面展示了如何实现这一扩展:

// 数据库日志记录器——实现Logger抽象类
public class DatabaseLogger extends Logger {
    public void log(String message) {
        // 示例逻辑来将消息保存到数据库
        System.out.println("Database logger: " + message);
        // 这里可以包含实际将日志保存到数据库的代码
    }
}

// 数据库日志工厂——继承LoggerFactory
public class DatabaseLoggerFactory extends LoggerFactory {
    @Override
    public Logger createLogger() {
        // 创建DatabaseLogger的逻辑,可以在这里包含初始化代码
        return new DatabaseLogger();
    }
}

        在Application 或其他任何需要日志记录功能的部分,现在可以不作出太多改动地简单地引入新的DatabaseLogger:

public class Application {
    public static void main(String[] args) {
        LoggerFactory factory = new DatabaseLoggerFactory();
        // 或者 factory = new FileLoggerFactory();
        // 或者 factory = new ConsoleLoggerFactory();
        factory.log("这是一条日志信息.");
    }
}

        现在,无论是FileLoggerFactoryConsoleLoggerFactory还是DatabaseLoggerFactory,均不需要修改 Application 中的任何代码。进一步说,如果有必要添加其他类型的日志记录,如远程API日志记录、XML日志记录等,整个流程同样适用。这就展示了工厂方法模式在扩展性方面的强大之处。每次新增一种产品(本例中的Logger实现),只需添加一个新的具体工厂类且不需要改动现有的代码,符合开闭原则(对修改封闭,对扩展开放)。

        通过上述示例,你可以看到工厂方法设计模式是如何工作的,它能够提供足够的灵活性,允许系统在不直接依赖具体类的情况下创建对象。这种方式降低了类间的耦合,提高了代码的可维护性与可扩展性。

        

二、模式讲解

2.1 功能

功能工厂方法主要工能是让父类在不知道具体实现的情况下,完成自身的功能调用;而具体的实现延迟到子类来实现

这样在设计的时候,不用去考虑具体的实现,需要某个对象,把它通过工厂方法返回就好了, 在使用这些对象实现功能的时候还是通过接口来操作,这类似于 IoC/DI 的思想。

        

2.2 工厂方法模式结构

  • Logger:定义工厂方法所创建的对象的类,也就是实际需要使用的对象的类。
  • 子类A:具体的 Logger 接口的实现对象。
  • Factory:创建器,声明工厂方法,工厂方法通常会返回一个 Logger 类型的实例对象,而且多是抽象方法。也可以在 Factory里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Logger类型的实现对象。
  • 实现类B:具体的创建器对象,覆盖实现 Factory 定义的工厂方法,返回具体的 Logger实例。

        

2.3 示例代码程序结构图

         

2.4 简单工厂方法结构图

         

2.5 工厂方法模式与简单工厂模式

        工厂方法模式与简单工厂模式结构如上图2 和 图3。

        简单工厂模式见:探索设计模式的魅力:简单工厂模式-CSDN博客文章浏览阅读1.5k次,点赞50次,收藏36次。实现简单工厂的难点就在于 “如何选择” 实现,前面便子中传递参数的方法, 那都是静态的参数,还可以实现成为动态的参数。客户端通过简单工厂创建 了一个实现接口的对象,然后面向接口编程,从客户端来看,它根本不知道具体的实现是什么,也不知道是如何实现的,它只知道通过工厂获得了一个接口对象 , 然后通过这个接口来获取想要的功能。如果通过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所代表的具体功能和含义,这样会增加客户端使用的难度,也部分暴露了内部实现,这种情况可以选用可配置的方式来实现。https://blog.csdn.net/danci_/article/details/135566105        若要添加新的日志类型,简单工厂模式需要添加这个新功能类的日志子类,再在Factory中添加一个case语句来做判断;工厂方法模式需要添加这个新功能类的日志子类,再添加一个实现Factory的工厂类。但要我再去更改客户端,这 不等于不但没有减化难度,反而增加了很多类和方法,把复杂性增加了 吗?为什么要这样?”

        简单工厂模式最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。但是如果添加新的日志类型,就要去个性case判断条件,这违背了开-闭原则。(工厂类扩展了,也修改了)

        于是工厂方法出现了。

工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

        此时,添加新的日志类型,只需要添加新的日志子炻和新的日志工厂类,满足了开-闭原则。(对扩展开放,对修改关闭)

        

三、扩展-工厂方法与IoC/DI

        IOC-Inversion of Control,控制反转。

        DI-Dependency Injection,依赖流入。

  3.1 弄明白IoC/DI

  1. 参与者:一般有三方参与者,一个是某个对象;另一个是IoC/DI 的容器;还有一个是某个对象的外部资源。

  2. 谁依赖于谁: 当然是某个对象依赖于 IoC/DI 的容器。

  3. 为什么需要依赖:对象需要 IoC/DI 的容器来提供对象需要的外部资源。

  4. 谁注入于谁:很明 显是 IoC/DI 的容器注入某个对象。

  5. 到底注入什么:就是注入某个对象所需要的外部资源。

  6. 谁控制谁:当然是 IoC/DI 的容器来控制对象了。

  7. 控制什么:主要是控制对象实例的创建。

  8. 为何叫反转 : 反转是相对于正向而言的,那么什么算是正向的呢? 考虑一下常规情况下的应用程序,如果要在A 里面使用C,你会怎么做呢? 当然是直接去创建C 的对象,也就是说,在A 类中主动去获取所需要的外部资源C,这种情况被称为正向的。

        那么什么是反向呢?就是A 类不再主动去获取C,而是被动等待,等待IoC/DI 的容器获 取一个C的实例,然后反向地注入到A 类中。

  9. 依赖注入和控制反转是同一概念吗?

        依赖注入和控制反转是对同一件事情的不同描述。 从某个方面讲,就是它们描述的角度不同。依赖注入是从应用程序的角度去描述,可以 把依赖注入描述得完整点 :应用程序依赖容器创建并注入它所需要的外部资源;而控制 反转是从容器的角度去描述,描述得完整点就是 :容器控制应用程序,由容器反向地向 应用程序注入其所需要的外部资源。

        小结:其实 IoC/DI 对编程带来的最大改变不是在代码 上,而是在思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击, 但是在 IoC/DI 思想中,应用程序就变成被动的了,被动地等待 IoC/DI 容器来创建并注入它所需要的资源了。

        

  3.2 工厂方法与IoC/DI的关系

        IoC/ DI:主从换位,被动等待IOC/DI容器来创建并流入它所需要的资源。

        工厂方法

/**
 * 工厂方法抽象类
 */
public abstract class LoggerFactory {

    /**
     * 操作日志
     */
    public void log(String message) {
        createLogger().log(message);
    }
    /**
     * 工厂方法,创建日志对象的接口对象
     */
    public abstract Logger createLogger();
}

        log方法中,需要用Logger类,可是又不知道要用哪一个,也不去主动去创建了,反正在子类里已经实现了,不用管怎么获取,直接使用日志功能,类似于从子类注入进来。   

        从思想层面上,会发现工厂方法示模式和 IoC/DI 的思想是相似的,都是“ 主动变被动” ,进行了“ 主从换位” ,从而获得了更灵活的程序结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/324782.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于物联网设计的智能储物柜(4G+华为云IOT+微信小程序)

一、项目介绍 在游乐场、商场、景区等人流量较大的地方,往往存在用户需要临时存放物品的情况,例如行李箱、外套、购物袋等。为了满足用户的储物需求,并提供更加便捷的服务体验,当前设计了一款物联网智能储物柜。 该智能储物柜通…

矩阵快速幂技巧练习(一)— 经典牛问题

上一篇文章简单介绍了斐波那契数列的矩阵乘法,并做了一个小推广,这篇文章来小试牛刀,做一个经典的练习题。 求斐波那契数列矩阵乘法的方法 题目 第一年农场有一只成熟的母牛A,往后的每年: 每一只成熟的母牛都会生一只…

MySQL面试题 | 09.精选MySQL面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

【Debian】非图形界面Debian10.0.0安装xfce和lxde桌面

一、安装 1. Debian10.0.0安装xfce桌面 sudo apt update sudo apt install xfce4 startxfce4 2. Debian10.0.0安装lxde桌面 sudo apt-get install lxde安装后重启电脑。 二、说明 XFCE、LXDE 和 GNOME 是三个流行的桌面环境,它们都是为类 Unix 操作系统设计…

C语言:编译和链接

目录 一:翻译环境和运行环境 二:翻译环境 2.1预处理(预编译) 2.2编译 2.2.1 词法分析: 2.2.2语法分析 2.2.3语义分析 2.3 汇编 三:运行环境 一:翻译环境和运行环境 在ANSI C的任何一种…

微信小程序------WXML模板语法之条件渲染和列表渲染

目录 前言 一、条件渲染 1.wx:if 2. 结合 使用 wx:if 3. hidden 4. wx:if 与 hidden 的对比 二、列表渲染 1. wx:for 2. 手动指定索引和当前项的变量名* 3. wx:key 的使用 前言 上一期我们讲解wxml模版语法中的数据绑定和事件绑定(上一期链接:…

MATLAB - 使用运动学 DH 参数构建机械臂

系列文章目录 前言 一、 使用 Puma560 机械手机器人的 Denavit-Hartenberg (DH) 参数,逐步建立刚体树形机器人模型。在连接每个关节时,指定其相对 DH 参数。可视化机器人坐标系,并与最终模型进行交互。 DH 参数定义了每个刚体通过关节与其父…

Go-gin-example 第二部分 jwt验证

文章目录 使用 JWT 进行身份校验jwt知识点补充认识JWTTOKEN是什么jwt的使用场景jwt的组成headerpayloadsignature 下载依赖包编写 jwt 工具包jwt中间件编写如何获取token 编写获取token的Apimodels逻辑编写路由逻辑编写修改路由逻辑 验证token将中间件接入Gin功能验证模块 续接…

gitlab 命令执行漏洞(CVE-2022-2992)

1.漏洞影响版本 GitLab CE/EE 中的一个漏洞影响从 11.10 开始到 15.1.6 之前的所有版本、从 15.2 开始到 15.2.4 之前的所有版本、从 15.3 开始到 15.3.2 之前的所有版本。允许经过身份验证的用户通过从 GitHub API 端点导入实现远程代码执行。 查看 gitlab 版本。(登录后才能…

【目标检测】YOLOv7算法实现(一):模型搭建

本系列文章记录本人硕士阶段YOLO系列目标检测算法自学及其代码实现的过程。其中算法具体实现借鉴于ultralytics YOLO源码Github,删减了源码中部分内容,满足个人科研需求。   本系列文章在YOLOv5算法实现的基础上,进一步完成YOLOv7算法的实现…

使用STM32Cube库开发USB虚拟串口设备

开发基于STM32Cube库的USB虚拟串口设备需要了解USB通信协议、虚拟串口设备的基本原理以及STM32Cube库的使用。在这篇文章中,我们将介绍如何利用STM32Cube库开发一个USB虚拟串口设备,并提供相应的代码示例。 1. USB虚拟串口设备概述 USB虚拟串口设备是指…

力扣刷题(无重复字符的最长子串)

3. 无重复字符的最长子串https://leetcode.cn/problems/longest-substring-without-repeating-characters/ 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是…

使用vue快速开发一个带弹窗的Chrome插件

vue-chrome-extension-quickstart 说在前面 🎈平时我们使用Chrome插件通常都只是用来编写简单的js注入脚本,大家有没有遇到过需要插件在页面上注入一个弹窗呢?比如我们希望可以通过快捷键快速唤起ChatGPT面板或者快速唤起一个翻译面板&#x…

动态规划:01背包问题(一)

本题力扣上没有,是刷的卡码网第46题感兴趣的小伙伴可以去刷一下,是ACM模式。本篇讲解二维dp数组来解决01背包问题,下篇博客将用一维dp数组来解决01背包问题。 题目: 46. 携带研究材料 时间限制:5.000S 空间限制&…

Spark---RDD持久化

文章目录 1.RDD持久化1.1 RDD Cache 缓存1.2 RDD CheckPoint 检查点1.3 缓存和检查点区别 2.RDD分区器2.1 Hash 分区:2.2 Range 分区:2.3 用户自定义分区 1.RDD持久化 在Spark中,持久化是将RDD存储在内存中,以便在多次计算之间重…

HDFS WebHDFS 读写文件分析及HTTP Chunk Transfer Encoding相关问题探究

文章目录 前言需要回答的首要问题DataNode端基于Netty的WebHDFS Service的实现基于重定向的文件写入流程写入一个大文件时WebHDFS和Hadoop Native的块分布差异 基于重定向的数据读取流程尝试读取一个小文件尝试读取一个大文件 读写过程中的Chunk Transfer-Encoding支持写文件使…

快慢指针-Floyd判圈算法

对于环形链表是否存在环的做法,普通算法可以通过额外Hash数组来存储链表元素,直到Hash数组中出现重复元素。时间复杂度O(n),空间复杂度O(n) Floyd判圈算法通过利用快慢指针的移动来实现,时间复杂度O(n)&am…

Elasticsearch:聊天机器人教程(二)

这是继上一篇文章 “Elasticsearch:聊天机器人教程(一)”的续篇。本教程的这一部分讨论聊天机器人实现中最有趣的方面,以帮助你理解它并对其进行自定义。 数据摄入 在此应用程序中,所有示例文档的摄取都是通过 flask …

搭建知识付费小程序平台:如何避免被坑,选择最佳方案?

随着知识经济的兴起,知识付费已经成为一种趋势。越来越多的人开始将自己的知识和技能进行变现,而知识付费小程序平台则成为了一个重要的渠道。然而,市面上的知识付费小程序平台琳琅满目,其中不乏一些不良平台,让老实人…

git提交报错:remote: Please remove the file from history and try again.

1. 报错信息 remote: error: File: fba7046b22fd74b77425aa3e4eae0ea992d44998 500.28 MB, exceeds 100.00 MB. remote: Please remove the file from history and try again. git rev-list --objects --all | grep fba7046b22fd74b77425aa3e4eae0ea992d44998 2. 分析原因 e…