简单工厂模式虽然简单,但存在一个很严重的问题。当系统中需要引入新产品时,由于静态 工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违 背“开闭原则”,
而且简单工厂类包含了大量的if—else 代码,导致维护和测试难度增大。如何实现增加新产品而不影响已有代码?工厂方法模式应运而生,本文将介绍 第二种工厂模式——工厂方法模式。
先看一下工厂方法模式的定义:
工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个 类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式 (Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式 (Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。
多读几遍定义 然后让我们根据定义分析一下工厂方法模式的结构:
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方 法,创建具体的产品对象。
根据结构我们上代码举个栗子:
/** * 抽象产品 */ interface Product { public void writeLog(); } /** *具体产品 具体产品可以有多个 */ class ConcreteProduct implements Product{ @Override public void writeLog() { } } /** * 抽象工厂 */ interface Factory { public Product factoryMethod(); } /** * 具体工厂 具体工厂可以有多个 */ class ConcreteFactory implements Factory { public Product factoryMethod() { return new ConcreteProduct(); } } /** * 客户端调用: */ public static void main(String[] args) { Factory factory; factory = new ConcreteFactory(); Product product; product = factory.factoryMethod(); } |
看到没有 我的工厂是new出来的 我为啥没有把工厂方法写成静态的?为什么? 跟简单工厂方法模式那样对象的创建和使用分离开不好吗?
不能,因为工厂方法实际上是抽象方法,要求由子类来动态地实现,而动态性与static所声明的静态性相冲突
与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是 接口,也可以是抽象类或者具体类
最后来个实际业务场景看看:
Sunny软件公司欲开发一个系统运行日志记录器(Logger),该记录器可以通过多种途径保存系 统的运行日志,如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志 记录方式。
工厂方法模式解决UML:
完整代码:
//日志记录器接口:抽象产品 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(); } } |
这时候Client这头我要切换日志打印形式怎么办?
把factory = new FileLoggerFactory(); 改成 factory = new DatabaseLoggerFactory ();
是不是违反开闭原则了,我们改代码了!
这种违反开闭原则的问题我们可以通过 配置文件+java反射的方式 规避它 这样 我们业务变动是不会变代码,只需变配置:
配置文件中:
<!— config.xml -->
<?xml version="1.0"?>
<config>
<className>FileLoggerFactory</className>
</config>
代码中:
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
工厂方法模式总结:
工厂方法模式的主要优点如下:
(1) 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体 产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚 至无须知道具体产品类的类名。
(2) 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确 定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模 式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
(3) 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品 提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具 体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
1. 主要缺点
工厂方法模式的主要缺点如下:
(1) 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统 中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行, 会给系统带来一些额外的开销。
(2) 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义, 增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统 的实现难度。
1. 适用场景 在以下情况下可以考虑使用工厂方法模式:
(1) 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的 类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类 的类名存储在配置文件或数据库中。
(2) 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要 提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和 里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。