详解Java设计模式之工厂方法模式
案例引入工厂方法模式
如图是使用简单工厂模式设计的按钮工厂
从上图使用简单工厂方法模式可以看到,一旦我们需要新增一个具体产品,就必须去修改工厂类,这必然会违反开闭原则。
而按照如下方式进行改进之后
经过改进之后,我们发现如果我们要新增一种产品,这里是指椭圆形按钮,我们只需要增加生产他的对应的工厂类就可以,就无需再去修改抽象按钮工厂。以上使用的改进模式称为工厂方法模式
我们现在简单分析一下在该场景下使用工厂方法模式的特点
- 显然我们现在不需要提供一个按钮工厂类来同一负责所有产品的创建,而是将具体的按钮的创建交给了专门的工厂子类去创建
- 如果出现新的按钮类型,只需要为这种新的按钮定义一个具体的工厂类,就可以创建该新按钮的时实例
- 显然满足开闭原则
工厂方法模式
定义
定义一个用于创建对象的接口,但是让子类去决定将哪一个类进行实例化。工厂方法模式就是让一个类的实例化延迟到其子类。
Facroty Method Pattern:Define an interface for creating an object,but let subclass decide which class to instantiaate.Factory Method let a class defer instaniation to subclasses.
-
属于类创建型模式
-
简称工厂模式,又可称虚拟构造器或者多态工厂模式。
-
工厂父类负责定义创建产品对象的接口,而工厂子类负责生成具体的产品对象
-
目的是将产品类的实例化过程延迟到工厂子类完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类
-
UML类图实现
- 抽象产品Product
- 具体产品ConcreteProduct
- 抽象工厂Factory
- 具体工厂ConcreteFactory
案例分析
某系统运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。
为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,现使用工厂方法模式设计该系统。
UML类图分析
代码分析
-
LoggerFacoty
package csu.edu.cn.designpattern.factorymethodpattern.factory; import csu.edu.cn.designpattern.factorymethodpattern.product.Logger; /** * @Author: PlusHuang * @Date: 2021/11/30 16:33 * @Theme:抽象工厂接口 * @Description:包含一个抽象工厂方法 */ public interface LoggerFactory { //工厂方法 Logger createLogger(); }
-
FileLoggerFactory
package csu.edu.cn.designpattern.factorymethodpattern.factory; import csu.edu.cn.designpattern.factorymethodpattern.product.FileLogger; import csu.edu.cn.designpattern.factorymethodpattern.product.Logger; /** * @Author: PlusHuang * @Date: 2021/11/30 16:35 * @Theme: 文件日志器记录器工厂 * @Description: */ public class FileLoggerFactory implements LoggerFactory{ @Override public Logger createLogger() { System.out.println("-----正在创建文件日志记录器----"); return new FileLogger(); } }
-
DataBaseLoggerFactory
package csu.edu.cn.designpattern.factorymethodpattern.factory; import csu.edu.cn.designpattern.factorymethodpattern.product.DataBaseLogger; import csu.edu.cn.designpattern.factorymethodpattern.product.Logger; /** * @Author: PlusHuang * @Date: 2021/11/30 16:36 * @Theme: 数据库日志器记录器工厂 * @Description: */ public class DataBaseLoggerFactory implements LoggerFactory{ @Override public Logger createLogger() { System.out.println("-----正在创建数据库日志记录器----"); return new DataBaseLogger(); } }
-
Logger
package csu.edu.cn.designpattern.factorymethodpattern.product; /** * @Author: PlusHuang * @Date: 2021/11/30 16:36 * @Theme: 抽象产品接口 * @Description: 包含一个写日志方法抽象方法 */ public interface Logger { void writeLog(); }
-
FileLogger
package csu.edu.cn.designpattern.factorymethodpattern.product; /** * @Author: PlusHuang * @Date: 2021/11/30 16:36 * @Theme: 文件日志记录器 * @Description: */ public class FileLogger implements Logger { @Override public void writeLog() { System.out.println("正在使用文件日志记录器记录日志"); } }
-
DataBaseLogger
package csu.edu.cn.designpattern.factorymethodpattern.product; /** * @Author: PlusHuang * @Date: 2021/11/30 16:36 * @Theme: 数据库日志记录器 * @Description: */ public class DataBaseLogger implements Logger{ @Override public void writeLog() { System.out.println("正在使用数据库日志记录器记录日志"); } }
-
Client
package csu.edu.cn.designpattern.factorymethodpattern; import csu.edu.cn.designpattern.factorymethodpattern.factory.LoggerFactory; import csu.edu.cn.designpattern.factorymethodpattern.product.Logger; import csu.edu.cn.designpattern.factorymethodpattern.util.XMLUtil; /** * @Author: PlusHuang * @Date: 2021/11/30 16:32 * @Theme: 客户端类 * @Description: 测试 */ public class Client { public static void main(String[] args) { LoggerFactory factory; Logger logger; //创建工厂类,并强制转换 factory = (LoggerFactory)XMLUtil.getBean(); //调用工厂方法,创建产品对象 logger = factory.createLogger(); //调用日志记录方法 logger.writeLog(); } }
-
config
<?xml version="1.0"?> <config> <className>csu.edu.cn.designpattern.factorymethodpattern.factory.FileLoggerFactory</className> </config>
-
XMLUtil
package csu.edu.cn.designpattern.factorymethodpattern.util; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; /** * @Author: PlusHuang * @Date: 2021/11/28 16:49 * @Theme: 配置文件+反射实现 对象生成 * @Description:工具类 */ public class XMLUtil { //该方法用于从XML配置文件中提取类的类型,并创建对象 public static Object getBean(){ try { //创建文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File("src/csu/edu/cn/designpattern/factorymethodpattern/config.xml")); //获取类名的文本节点 NodeList nl = doc.getElementsByTagName("className"); Node classNode = nl.item(0).getFirstChild(); String className = classNode.getNodeValue().trim(); //通过类名生成实例对象并返回 Class c = Class.forName(className); Object o = c.newInstance(); return o; } catch(Exception e) { e.printStackTrace(); return null; } } }
运行结果
-----正在创建文件日志记录器---- 正在使用文件日志记录器记录日志
-
如何在此基础上要新增一个产品,该如何改动呢?方案如下:
(1) 增加一个新的具体产品类作为抽象产品类的子类
(2) 增加一个新的具体工厂类作为抽象工厂类的子类,该工厂用于创建新增的具体产品对象
(3) 修改配置文件,用新的具体工厂类的类名字符串替换原有工厂类类名字符串
(4) 编译新增具体产品类和具体工厂类,运行客户端代码,即可完成新产品的增加和使用
工厂方法的重载
public 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;
}
}
//其他具体工厂类代码省略
工厂方法的隐藏
-
目的:为了进一步简化客户端的使用
-
实现:在工厂类中直接调用产品类的业务方法,客户端无须调用工厂方法创建产品对象,直接使用工厂对象即可调用所创建的产品对象中的业务方法
//将接口改为抽象类
public abstract class LoggerFactory {
//在工厂类中直接调用日志记录器类的业务方法writeLog()
public void writeLog() {
Logger logger = this.createLogger();
logger.writeLog();
}
public abstract Logger createLogger();
}
//客户端修改
public class Client {
public static void main(String args[]) {
LoggerFactory factory;
factory = (LoggerFactory)XMLUtil.getBean();
factory.writeLog(); //直接使用工厂对象来调用产品对象的业务方法
}
}
模式优点
- 工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节
- 能够让工厂自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
- 在系统中加入新产品时,完全符合开闭原则
模式缺点
- 系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
- 增加了系统的抽象性和理解难度
模式适用环境
- 客户端不知道它所需要的对象的类(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体产品对象由具体工厂类创建)
- 抽象工厂类通过其子类来指定创建哪个对象