1、定义
工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
2、形式
工厂方法模式的通用类图如下所示:
上图中各类和接口的含义为:
- 抽象产品类
Product
负责定义产品的共性,实现对事物最抽象的定义 - 抽象创建类
Creator
即为抽象工厂 - 具体创建类
ConcreteCreator
为具体的实际工厂
一个通用的模板代码如下:
Product
public abstract class Product {
//产品类的公共方法
public void method1(){
//业务处理逻辑
}
//抽象方法
public abstract void method2();
}
Creator
public abstract class Creator {
/*
* 创建一个产品对象,输入参数自定义
* */
public abstract <T extends Product> T createProduct(Class<T> c);
}
ConcreteCreator
public class ConcreteCreator extends Creator{
public <T extends Product> T createProduct(Class<T> c) {
Product product = null;
try{
product = (Product) Class.forName(c.getName()).newInstance();
}catch (Exception e){
//异常处理
}
return (T)product;
}
}
ConcreteProduct1
public class ConcreteProduct1 extends Product{
public void method2() {
//业务处理逻辑
}
}
ConcreteProduct2
public class ConcreteProduct2 extends Product{
public void method2() {
//业务处理逻辑
}
}
Client
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct1.class);
/*
* 其他业务代码
* */
}
}
3、优缺点
优点
- 良好的封装性,代码结构清晰
调用者需要一个对象时只需要知道该产品的类名(或字符串别名),工厂类隐藏了创建对象的细节,降低了模块间的耦合。
- 代码的扩展性大大提高
在需要增加产品类时,只需要适当的修改具体工厂类或者再扩展一个工厂类,就能满足需求。
- 屏蔽了产品类
产品类的实现的变化无需调用关心,只要保持接口不变,系统中的上层模块就不需要发生改变。由于产品类的实例化工作是由工厂类负责,因此一个产品对象具体由哪一个产品类生成由工厂类决定,例如在数据库开发中,使用JDBC连接数据库,如果数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动的名称,这显著体现了工厂模式的灵活性。
工厂方法模式是一个典型的解耦框架,这种设计模式符合迪米特法则(对其他类有最少的了解),符合依赖倒置原则(只依赖产品类的抽象),也符合里氏替换原则(产品子类替换产品父类)
缺点:在一定程度上增加了代码的复杂度。
4、使用场景
场景一
工厂方法模式主要用来生成产品对象,因此在所有需要生成对象的地方都可以使用工厂方法替代,但使用时要结合实际场景考虑是否需要工厂类进行管理,避免随意应用导致代码复杂度增加。
场景二
需要可以灵活扩展的框架时,可以考虑采用工厂方法模式。例如设计一个连接邮件服务器的框架,有三种网络协议可以供系统选择,那么就可以将三种连接方法作为产品类,定义一个接口类作为抽象产品,在用不同的方法实现三个具体的产品类,最后定义一个工厂方法,按不同的传入条件选择不同的连接方式。此后如果增加协议就可以简单地通过增加一个产品类实现。
场景三
用于异构项目。对于一些需要与非java交互的异构系统可以考虑采用工厂方法模式。例如对于一个从非java文件(例如XML)中产生的对象,可以视为一个产品,使用工厂方法模式进行封装管理,减小系统与外部系统的耦合。
场景四
用于测试。在测试开发的背景下,某类进行单元测试时可以通过工厂方法模式将相关联的类虚拟出来。
5、扩展
1、简单(静态)工厂模式
在一些比较的简单的情况下,一个模块只需要一个工厂类,没有必要定义工厂类的抽象接口,因此简单工厂模式就诞生了。
简单工厂模式只包含实际工厂类,并且将工厂方法设置为static
类型,这样便可以直接通过工厂类类名调用工厂方法。对比原工厂模式,简单工厂的类和调用都变得简单,但其缺点是工厂类的扩展比较困难,不符合开闭原则,因此适用于一些比较简单且变化不大的模块。
2、多工厂模式
在一些复杂项目中,对象的初始化可能要做许多工作,每个产品类的初始化方法都不同,那么如果只有一个工厂,那么只能把所有不同的实现写在一个工厂方法中,并且通过识别不同的入参调用不同的逻辑代码,这样不仅导致代码冗长,而且可读性极差。因此为了保证结构清晰,如上类图所示,为每一个产品定义一个工厂类,然后由调用者自行选择与哪个工厂方法关联(不用给createProduct
显示传参)。由于每个工厂都独立负责创建对应的产品对象,这非常符合单一职责原则的要求。
虽然多工厂模式使得类的职责清晰、结构简单,但给可扩展性和可维护性带来了一定的影响,因为增加一个产品必须建立一个相应的工厂类,维护时也需要考虑两个对象之间的关系。
注:在一些复杂的应用中,一般采用多工厂模式加一个协调类的做法来避免调用者与各个子工厂类交流,协调类的作用是封装子工厂类,对高层模块提供统一的访问接口,避免高低层的耦合。
3、替代单例模式
可以采用工厂方法模式实现单例模式,类图如下所示:
由于单例类的构造方法是私有的,因此无法通过正常的渠道建立一个单例对象,因此只能在工厂中通过反射方式获取一个单例对象,代码如下:
Singleton
public class Singleton {
private Singleton(){}
public void doSomething(){
/*业务代码*/
}
}
SingletonFactory
public class SingletonFactory {
private static Singleton singleton;
static {
try {
Class cl = Class.forName(Singleton.class.getName());
Constructor constructor = cl.getDeclaredConstructor();
//设置构造器是可访问的
constructor.setAccessible(true);
singleton = (Singleton)constructor.newInstance();
} catch (Exception e){
//异常处理
}
}
public static Singleton getSingleton(){
return singleton;
}
}
该框架也可以扩展,变成项目中的单例构造器,所有需要产生单例的类都遵守一定的规则,通过传入特定类型给扩展后的工厂类,便可获得一个唯一的实例。
注:通过反射得到类实例并不是工厂方法的特权,其他类也可以通过该方式建立单例对象,但在实际开发过程中,在团队章程和规范的约束下几乎不会发生这种情况。
4、对象缓存
工厂模式可以用来缓存(固定大小)对象,通过容器保留生成的对象,在需要时直接取出使用,而不需要再次生成,避免对象的频繁销毁和产生。该模式下的类图为:
工厂类定义如下:
public class ProductFactory {
private static final Map<String,Product> prMap = new HashMap();
public static synchronized Product createProduct(String type) throws Exception{
Product product = null;
if(prMap.containsKey(type)){
product = prMap.get(type);
}else {
if(type.equals("Product1")){
product = new ConcreteProduct1();
}else {
product = new ConcreteProduct2();
}
prMap.put(type,product);
}
return product;
}
}
这种用法的一个例子就是JDBC连接数据库时的连接对象的创建,使用前会设置一个MaxConnections
最大连接数,这个就是内存中最大的实例化数量。
6、小结
工厂方法模式在项目中使用频繁,它有效地减低了代码的耦合度。并且,工厂模式可以与其他模式混合使用(例如单例、模板、原型等)生成优秀的设计,应当熟练掌握。