设计模式之工厂方法模式解析

工厂方法模式
1)问题

简单工厂模式

当需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,需要修改工厂类的源代码。

2)概述

针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。

在这里插入图片描述

3)角色
Product(抽象产品):定义产品的接口,是工厂方法模式所创建对象的超类型,是产品对象的公共父类。ConcreteProduct(具体产品):实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。ConcreteFactory(具体工厂):是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

代码示例

interface Factory {public Product factoryMethod();
}class ConcreteFactory implements Factory {public Product factoryMethod() {return new ConcreteProduct();}
}

注意

在实际使用时,具体工厂类在实现工厂方法时除了创建具体产品对象之外,还可以负责产品对象的初始化及一些资源和环境的配置工作,例如连接数据库、创建文件等。

可以通过配置文件来存储具体工厂类 ConcreteFactory 的类名,更换新的具体工厂时无须修改源代码,系统扩展更为方便。

4)案例-初始方案

问题

工厂类包含大量的if…else…代码,导致维护和测试难度增大;

系统扩展不灵活,增加新类型的日志记录器,必须修改静态工厂方法的业务逻辑。

在这里插入图片描述

5)案例-重构后

Logger 接口充当抽象产品,其子类 FileLogger 和 DatabaseLogger 充当具体产品,LoggerFactory 接口充当抽象工厂,其子类FileLoggerFactory 和 DatabaseLoggerFactory 充当具体工厂。

在这里插入图片描述

//日志记录器接口:抽象产品
interface Logger {public void writeLog();
}//数据库日志记录器:具体产品
class DatabaseLogger implements Logger {public void writeLog() {System.out.println("数据库日志记录。");}
}//文件日志记录器:具体产品
class FileLogger implements Logger {public void writeLog() {System.out.println("文件日志记录。");}
}//日志记录器工厂接口:抽象工厂
interface LoggerFactory {public Logger createLogger();
}//数据库日志记录器工厂类:具体工厂
class DatabaseLoggerFactory implements LoggerFactory {public Logger createLogger() {//连接数据库,代码省略//创建数据库日志记录器对象Logger logger = new DatabaseLogger(); //初始化数据库日志记录器,代码省略return logger;}	
}//文件日志记录器工厂类:具体工厂
class FileLoggerFactory implements LoggerFactory {public Logger createLogger() {//创建文件日志记录器对象Logger logger = new FileLogger(); //创建文件,代码省略return logger;}	
}class Client {public static void main(String args[]) {LoggerFactory factory;Logger logger;factory = new FileLoggerFactory(); //可引入配置文件实现logger = factory.createLogger();logger.writeLog();}
}
6)案例-使用反射和配置文件优化

在客户端代码中不再使用 new 关键字创建工厂对象,而是将具体工厂类的类名存储在配置文件中,通过读取配置文件获取类名字符串,再使用 Java 的反射机制,根据类名字符串生成对象。

//通过类名生成实例对象并将其返回
Class c=Class.forName("String");
Object obj=c.newInstance();
return obj;

配置文件 config.xml 存储具体工厂类类名

<!— config.xml -->
<?xml version="1.0"?>
<config><className>FileLoggerFactory</className>
</config>

XML工具类

//工具类XMLUtil.java
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;public class XMLUtil {
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象public static Object getBean() {try {//创建DOM文档对象DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = dFactory.newDocumentBuilder();Document doc;							doc = builder.parse(new File("config.xml")); //获取包含类名的文本节点NodeList nl = doc.getElementsByTagName("className");Node classNode=nl.item(0).getFirstChild();String cName=classNode.getNodeValue();//通过类名生成实例对象并将其返回Class c=Class.forName(cName);Object obj=c.newInstance();return obj;}   catch(Exception e) {e.printStackTrace();return null;}}
}

客户端代码

class Client {public static void main(String args[]) {LoggerFactory factory;Logger logger;factory = (LoggerFactory)XMLUtil.getBean(); //getBean()的返回类型为Object,需要进行强制类型转换logger = factory.createLogger();logger.writeLog();}
}

引入XMLUtil类和XML配置文件后,增加新的日志记录方式,只需如下步骤

新的日志记录器需要继承抽象日志记录器Logger;

对应增加一个新的具体日志记录器工厂,继承抽象日志记录器工厂LoggerFactory,并实现其中的工厂方法createLogger(),返回具体日志记录器对象;

修改配置文件config.xml,将新增的日志记录器工厂类的类名替换原有工厂类类名;

编译新增的日志记录器类和日志记录器工厂类,原有类库代码无须做任何修改。

7)案例-使用方法重载和隐藏进行优化
1.方法重载

在这里插入图片描述

引入重载方法后,抽象工厂 LoggerFactory 的代码如下:

interface LoggerFactory {public Logger createLogger();public Logger createLogger(String args);public Logger createLogger(Object obj);
}

具体工厂类 DatabaseLoggerFactory 的代码如下:

class DatabaseLoggerFactory implements LoggerFactory {public Logger createLogger() {//使用默认方式连接数据库,代码省略Logger logger = new DatabaseLogger(); //初始化数据库日志记录器,代码省略return logger;}public Logger createLogger(String args) {//使用参数args作为连接字符串来连接数据库,代码省略Logger logger = new DatabaseLogger(); //初始化数据库日志记录器,代码省略return logger;}	public Logger createLogger(Object obj) {//使用封装在参数obj中的连接字符串来连接数据库,代码省略Logger logger = new DatabaseLogger(); //使用封装在参数obj中的数据来初始化数据库日志记录器,代码省略return logger;}	
}
2.方法隐藏

为进一步简化客户端的使用,还可以对客户端隐藏工厂方法;

在工厂类中调用产品类的业务方法,客户端无须调用工厂方法创建产品,直接通过工厂即可使用所创建的对象中的业务方法。

在这里插入图片描述

抽象工厂类 LoggerFactory 的代码如下:

//改为抽象类
abstract class LoggerFactory {//在工厂类中直接调用日志记录器类的业务方法writeLog()public void writeLog() {Logger logger = this.createLogger();logger.writeLog();}public abstract Logger createLogger();	
}

客户端代码修改如下:

class Client {public static void main(String args[]) {LoggerFactory factory;factory = (LoggerFactory)XMLUtil.getBean();factory.writeLog(); //直接使用工厂对象来调用产品对象的业务方法}
}
8)总结
1.优点

只需要关心所需产品对应的工厂,无须关心创建细节,无须知道具体产品类的类名。

在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,只要添加一个具体工厂和具体产品就可以。

2.缺点

添加新产品时,需要编写新的具体产品类,还要提供与之对应的具体工厂类,系统中类的个数将成对增加,会给系统带来额外开销。

考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层定义,增加了系统的抽象性和理解难度,且在实现时需要用到DOM、反射等技术,增加了系统的实现难度。

3.适用场景

客户端不需要知道具体产品类的类名,只需要知道对应的工厂即可。

抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我的保研材料全部损坏了!这个压缩包文件格式未知或数据已经被损坏不可预料的压缩文件末端

求助各位友友&#xff0c;我的保研材料全部没了&#xff01; 之前为了清理D盘&#xff0c;把之前保研期间准备的几个G的材料全部压缩放在了U盘&#xff0c;但是现在却损坏打不开了&#xff0c;之前为了省事也没有添加过“恢复记录”&#xff01;&#xff01;&#xff01; 先声…

阿赵UE学习笔记——20、角色蓝图和动画蓝图

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。   继续学习虚幻引擎的使用。这次来看看角色控制动画相关的东西&#xff0c;主要用到了动画蓝图和角色蓝图。 一、动画蓝图 之前分析过&#xff0c;蓝图对于虚幻引擎来说&#xff0c;是存在于各个系统里面的&#xff0c;相当…

js 替换数组中的部分文字内容

用js 把[ "2024-03-20实时", "2024-03-20日前", "运行日实时", "运行日日前"]中把所有的“运行日”替换成 “2023” 可以使用 JavaScript 的 Array.prototype.map() 方法来遍历数组&#xff0c;并使用 String.prototype.replace() 方…

【0274】从shared init file或local init file加载relation cache(2 - 1)

上一篇: 【0273】深入分析 relcache(relation descriptor cache)初始化第一阶段(1) 【0264】深入分析relcache(relation descriptor cache)缓存初始化第2阶段(2) 1. 前言 本文内容是作为《【0264】深入分析relcache(relation descriptor cache)缓存初始化第2阶段…

智慧公厕:卫生、便捷、安全的新时代厕所变革

在城市快速发展的背景下&#xff0c;公共厕所的建设和管理变得越来越重要。智慧公厕作为厕所变革的一项全新举措&#xff0c;通过建立公共厕所全面感知监测系统&#xff0c;以物联网、互联网、大数据、云计算、自动化控制技术为支撑&#xff0c;实现对公共厕所的智能化管理和运…

FPGA学习_时序约束以及VIVADO时序报告

文章目录 前言时序约束的目的一、时序约束种类1、约束主时钟2、约束衍生时钟3、约束虚拟时钟4、input delay5、output delay6、约束异步时钟组7、约束互斥时钟8、假路径约束9、多周期约束 二、VIVADO时序报告三、从时序的角度看为什么寄存器赋值慢一拍 前言 一边学习一边补充当…

消除 Git diff 中的换行符差异(Linux)

通常编辑器默认使用的换行符是跟随操作系统的&#xff0c;而windows操作系统上修改的代码&#xff0c;其换行符会被转成win的\r\n,在提交代码时会显示大量改动&#xff08;对于sh脚本还会存在无法执行的问题&#xff09;&#xff0c;这时候我们可以通过设置git自动转成unix格式…

容器中的大模型(三)| 利用大语言模型:容器化高效地部署 PDF 解析器实践...

作者&#xff1a;宋文欣&#xff0c;智领云科技联合创始人兼CTO 01 简介 大语言模型&#xff08;LLMs&#xff09;正逐渐成为人工智能领域的一颗璀璨明星&#xff0c;它们的强大之处在于能够理解和生成自然语言&#xff0c;为各种应用提供了无限可能。为了让这些模型更好地服务…

【Hadoop】Hadoop 编译源码

目录 为什么要源码编译Hadoop 编译源码1前期工作准备2jar 包安装2.1安装 Maven2.2安装 ant2.3安装 glibc-headers 和 g2.4安装 make 和 cmake2.5安装 protobuf2.6安装 openssl 库2.7安装 ncurses-devel 库 3编译源码3.1解压源码到 /opt/ 目录3.2 进入到 hadoop 源码主目录 /opt…

原生小程序开发的父子组件传值,兄弟组件传值

1.父子传值&#xff0c;父组件通过属性的方式去给子组件传递值&#xff0c;子组件在properties属性去接收父组件传递过来的值&#xff1a; 父组件部分&#xff1a; <view class"pcolor"><customer id"child" bind:changSex"changSex"…

AI时代Python金融大数据分析实战:ChatGPT让金融大数据分析插上翅膀

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

UniTask 异步任务

文章目录 前言一、UniTask是什么&#xff1f;二、使用步骤三、常用的UniTask API和示例1.编写异步方法2.处理异常3.延迟执行4.等待多个UniTask或者一个UniTas完成5.异步加载资源示例6.手动控制UniTask的完成状态7.UniTask.Lazy延迟任务的创建8.后台线程切换Unity主线程9.不要返…

第二证券|热度飙升,出境游人数有望破亿,这些概念股被机构盯上

在免签和航班批量康复的方针利好下&#xff0c;本年出境游商场迎来炽热升温。 清明出境游有望爆火 3月20日&#xff0c;Airbnb爱彼迎在北京举行春季出境游趋势发布会举行。 爱彼迎中国数据显示&#xff0c;本年清明节期间的出境游查找热度已经超出2023年同期的2.5倍&#xf…

计算机网络面经-什么是IPv4和IPv6?

前言 Internet协议&#xff08;IP&#xff09;是为连接到Internet网络的每个设备分配的数字地址。它类似于电话号码&#xff0c;是一种独特的数字组合&#xff0c;允许用户与他人通信。IP地址主要有两个主要功能。首先&#xff0c;有了IP&#xff0c;用户能够在Internet上被识别…

选择排序-python实现

选择排序是一种简单直观的排序算法&#xff0c;它的工作原理如下&#xff1a; 1、 在未排序序列中找到最小&#xff08;或最大&#xff09;元素&#xff0c;存放到排序序列的起始位置。 2、再从剩余未排序元素中继续寻找最小&#xff08;或最大&#xff09;元素&#xff0c;然…

Redis 安装(二)

Redis安装说明 大多数企业都是基于Linux服务器部署项目&#xff0c;而且Redis官网也没有提供Windows版本的安装包&#xff0c;因此课程中我们会基于Linux系统来安装Redis。 此处选择Linux版本为Centos7。 Redis的官方网站地址&#xff1a;https://redis.io/ Redis的安装 切换…

TCP协议中的传输控制机制图文详解「重传机制」「流量控制」「拥塞控制」

目录 TCP重传机制 超时重传 快速重传 SACK 方法 Duplicate SACK TCP 流量控制 滑动窗口 累积确认 窗口大小由哪一方决定&#xff1f; 接收窗口和发送窗口的大小是相等的吗&#xff1f; 流量控制 窗口关闭的后果 糊涂窗口综合症 TCP拥塞处理 为什么要有拥塞控制呀&#xff0c;不…

如何在WSL中的ubuntu编译Linux内核并且安装使用ebpf?

如何在WSL中的ubuntu编译Linux内核并且安装使用ebpf? 步骤1 编译安装内核获取源码修改配置编译编译成功后配置重启WSL测试 步骤2 安装bcc安装依赖下载bcc&#xff0c;编译测试 环境: wsl2windows 11 步骤1 编译安装内核 去https://kernel.org/找你想要的版本&#xff0c; …

119.设计链表(力扣)

代码解决 class MyLinkedList { public:// 定义链表节点结构体struct LinkedNode {int val;LinkedNode* next;LinkedNode(int val):val(val), next(nullptr){}};MyLinkedList() {dummyhead new LinkedNode(0);size0;}int get(int index) {if (index > (size - 1) || index…

从点云创建 DSM:网格化和可视化实用指南

今天我将向您展示如何从点云创建数字表面模型&#xff08;DSM&#xff09;。首先&#xff0c;我们将尝试了解 DSM 是什么&#xff0c;然后我们将进入讨论的更实际部分。 什么是 DSM&#xff1f; DSM 是一个描述表面及其表面所有内容的模型。现在&#xff0c;为了更清楚地了解…