工厂方法模式
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.适用场景
客户端不需要知道具体产品类的类名,只需要知道对应的工厂即可。
抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。