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

时间:2024-03-29 09:04:34
工厂方法模式
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.适用场景

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

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