工厂方法模式
定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化,工厂方法模式让一个类的实例化延迟到子类,它是一种类创建型模式
类图
实现
抽象工厂
package design.factory;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public interface Factory {
public Product factoryMethod();
}
抽象产品
package design.factory;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public interface Product {
}
具体产品
package design.factory;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class ConcreteProduct implements Product {
}
具体工厂
package design.factory;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class ConcreteFactory implements Factory {
public Product factoryMethod(){
return new ConcreteProduct();
}
}
客户端
package design.factory;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class Client {
public static void main(String[] args) {
Factory factory;
//可通过配置文件与反射机制实现
factory=new ConcreteFactory();
Product product;
product=factory.factoryMethod();
}
}
应用实例
某系统运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。
为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,现使用工厂方法模式设计该系统。注:writeLog()方法的实现只写简单的输出语句。
代码实现
抽象产品
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public interface Logger {
public void writeLog();
}
具体产品
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class DatabaseLogger implements Logger {
public void writeLog() {
System.out.println("数据库日志记录");
}
}
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class FileLogger implements Logger {
public void writeLog() {
System.out.println("文件日志记录");
}
}
抽象工厂
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public interface LoggerFactory {
public Logger createLogger();
}
具体工厂
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class DatabaseLoggerFactory implements LoggerFactory {
public Logger createLogger() {
Logger logger = new DatabaseLogger();
return logger;
}
}
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class FileLoggerFactory implements LoggerFactory {
public Logger createLogger() {
Logger logger = new FileLogger();
return logger;
}
}
客户端
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class Client {
public static void main(String[] args) {
LoggerFactory factory;
Logger logger;
factory = new FileLoggerFactory();
logger = factory.createLogger();
logger.writeLog();
}
}
存在问题:如果需要更换日志记录器(具体工厂类),需要修改客户端代码,不太符合开闭原则
解决:通过引入配置文件并使用反射机制实现在不修改客户端代码的基础上更换具体工厂类
增加config.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<config>
<className>design.factory.test.FileLoggerFactory</className>
</config>
增加工具类
package design.factory.test;
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;
public class XMLUtil {
public static Object getBean() {
try {
// 创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//design//factory//test//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;
}
}
}
客户端代码为
package design.factory.test;
/*
*
*@author:zzf
*@time:2020-12-19
*
*/
public class Client {
public static void main(String[] args) {
LoggerFactory factory;
Logger logger;
//factory = new FileLoggerFactory();
factory = (LoggerFactory) XMLUtil.getBean();
logger = factory.createLogger();
logger.writeLog();
}
}
进一步优化
尝试修改工厂基类,实现日志家族树的透明化,即客户端完全不知道日志家族树的任何信息(提示:采用工厂方法隐藏)
工厂方法隐藏
有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时工厂类中直接调用产品类的业务方法,客户端无须调用工厂方法创建产品对象,直接使用工厂对象即可调用所创建的产品对象中的业务方法
代码实现
将抽象工厂接口改为抽象类
public abstract class LoggerFactory {
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();
}
}
工厂方法模式的优缺点
优点:
(1)提供了专门的工厂方法来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节
(2)基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂自主确定创建何种产品对象,而如何创建这个对象的细节完全封装在具体工厂内部
(3)在系统加入新产品时完全符合开闭原则
缺点:
(1)系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
(2)增加了系统的抽象性和理解难度
适用环境
(1)客户端不知道它所需要的对象的类
(2)抽象工厂类通过其子类来指定创建哪个对象
参考
Java设计模式