创建型模式其二:《工厂方法模式》
工厂方法模式有啥用?为啥学?
之前的文章【一文通关】创建型模式其一: 简单工厂模式 - 掘金 (juejin.cn)
我们讲解了简单工厂模式,但是简单工厂模式缺点比较明显:
当我们想要添加新的具体类,必须要修改工厂核心代码:
工厂模式是来解决这个问题的
分析:
- 不再提供一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成
- 如果出现新的按钮类型,只需要为这种新类型的按钮定义一个具体的工厂类就可以创建该新按钮的实例
- 满足开闭原则
先把是什么给大家讲,这样更容易理解繁琐的定义:
工厂方法模式的定义
- 简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)
- 工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象
- 目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类
工厂方法模式的结构:
了解概念之后,看一个具体的例子,毕竟我们学是为了使用:
某系统运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。 为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,现使用工厂方法模式设计该系统。
抽象产品类
定义基本接口,让具体产品类实现
//日志记录器接口,充当抽象产品角色
public interface Logger {
public void writeLog();
}
具体产品类
//数据库日志记录器,充当具体产品角色
public class DatabaseLogger implements Logger {
public void writeLog() {
System.out.println("数据库日志记录。");
}
}
//文件日志记录器,充当具体产品角色
public class FileLogger implements Logger {
public void writeLog() {
System.out.println("文件日志记录。");
}
}
设计工厂接口类
既然我们总工厂只是一个壳,用于指定哪个子工厂创建,我们可以将其设置为抽象类或者接口类
//日志记录器工厂接口,充当抽象工厂角色
public interface LoggerFactory {
public Logger createLogger(); //抽象工厂方法
}
具体工厂类:(用于创建具体产品的具体工厂)
//数据库日志记录器工厂类,充当具体工厂角色
public class DatabaseLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//连接数据库,代码省略
//创建数据库日志记录器对象
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
}
//文件日志记录器工厂类,充当具体工厂角色
public class FileLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//创建文件日志记录器对象
Logger logger = new FileLogger();
//创建文件,代码省略
return logger;
}
}
客户端代码
我们可以在客户端指定选用的日志类型,也可以选用XML文件去配置,我们先选用客户端指定的方式:
public class Client {
public static void main(String args[]) {
LoggerFactory factory;
Logger logger;
factory = new DatabaseLoggerFactory();
logger = factory.createLogger();
logger.writeLog();
}
}
到这里就算完成基本功能了
输出:
使用XML文件的方式
同样的,我们使用XML文件作为配置
<?xml version="1.0"?>
<config>
<className>designpatterns.factorymethod.FileLoggerFactory</className>
</config>
XML文件读取类
import javax.xml.parsers.*;
import org.w3c.dom.*;
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().trim(); //类名字符串
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.getConstructor().newInstance();
return obj;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
修改客户端
public class Client {
public static void main(String args[]) {
LoggerFactory factory;
Logger logger;
// 通过Xml文件来获得具体的工厂类
factory = (LoggerFactory)XMLUtil.getBean(); //getBean()的返回类型为Object,需要进行强制类型转换
logger = factory.createLogger();
logger.writeLog();
}
}
输出:
我们是通过反射来生成实例类的。所以大家在写XML文件时,类名需要是完整的包名加类名
实践之后来总结优缺点
模式优点:
- 工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节
- 能够让工厂自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
- 在系统中加入新产品时,完全符合开闭原则
模式缺点:
- 系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
- 增加了系统的抽象性和理解难度
模式适用环境:
- 客户端不知道它所需要的对象的类(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体产品对象由具体工厂类创建)
- 抽象工厂类通过其子类来指定创建哪个对象
好了,其二篇到此结束,相信大家已经学会了。
下一篇,创建型模式其三:【一文通关】创建型模式其三: 抽象工厂模式 - 掘金 (juejin.cn)