简单记录 设计模式之禅-秦小波 & 软件秘笈-设计模式那些事-郑阿奇
文章目录
1、 工厂方法模式定义
简介:工厂方法模式是使用非常广泛的设计模式,在日常的开发中总能见到它的身影。
所谓工厂方法模式(Factory Method Pattern),就是定义一个创建产品对象的工厂接口,让子类决定实例化哪一种实例对象,也就是将实际创建实例对象的工作推迟到子类当中,核心工厂类不再负责具体产品的创建。
Define an interface for creating an object,but let subclasses decide which class toinstantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)
工厂方法模式是对简单工厂模式进行了抽象。如此一来核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。工厂方法模式完全实现“开-闭”原则,对扩展开放,对修改关闭。在不影响现有软件结构的基础上实现了扩展,可应用于更复杂的层次结构和产品结果复杂的场合。
工厂方法模式的通用类图。
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。
工厂方法模式的变种较多,我们来看一个比较实用的通用源码。
抽象产品类
/**
* 抽象产品类
*/
public abstract class Product {
//产品类的公共方法
public void method1(){
//业务逻辑处理
}
//抽象方法1
public abstract void method2();
}
抽象产品类
-
产品类的公共方法
-
抽象方法
具体的产品类可以有多个,都继承于抽象产品类。
具体产品类
抽象方法
/**
*
* 具体的产品类
*/
public class ConcreteProduct1 extends Product {
public void method2() {
//业务逻辑处理
}
}
/**
*
* 具体的产品类
*/
public class ConcreteProduct2 extends Product {
public void method2() {
//业务逻辑处理
}
}
抽象工厂类负责定义产品对象的产生
抽象工厂类
Creator
public abstract class Creator {
/*
* 创建一个产品对象,其输入参数类型可以自行设置
* 通常为String、Enum、Class等,当然也可以为空
*/
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;
}
}
场景类的调用方法
场景类
Clinet
public class Client {
private static Creator creator;
private static Product product;
public static void main(String[] args) {
creator = new ConcreteCreator();
product = creator.createProduct(ConcreteProduct1.class);
/*
* 继续业务处理
*/
}
}
该通用代码是一个比较实用、易扩展的框架,我们可以根据实际项目需要进行扩展。
2、工厂方法模式分析
2.1、角色分析
看工厂方法模式的定义:定义一个创建产品对象的工厂接口,让子类决定实例化哪一种实例对象,也就是将实际创建实例对象的工作推迟到子类当中,核心工厂类不再负责具体产品的创建。
我们可以这样理解工厂方法模式:由一个抽象工厂定义了创建产品的接口,但是具体是哪一种产品抽象工厂是不知道的,产品的实例化工作被推迟到了具体工厂当中。
在工厂方法模式中,存在这样几个角色:
(1)抽象工厂;Creator
(2)抽象产品;Product
(3)具体工厂;ConcreteCreator
(4)具体产品。ConcreteProduct
我们可以这样理解工厂方法设计模式:
(1)抽象工厂生产抽象产品(生产Product),
product = creator.createProduct(ConcreteProduct1.class);
具体工厂生产具体产品(ConcreteCreator类生产ConcreteProduct);
(2)抽象对具体(具体类实现抽象类,如ConcreteCreator类实现Creator;ConcreteProduct类实现Product类)。
抽象工厂 具体工厂
具体工厂实例化哪一种具体产品
抽象工厂中应该含有一个生产抽象产品的接口方法,该方法的返回值类型是抽象产品类型。抽象产品中应该含有共有属性方法和抽象方法。具体工厂就是负责生产具体产品的,它实现了抽象工厂接口方法,返回具体类型具体产品的。
具体产品是抽象产品的子类,具体工厂实现了抽象工厂接口方法。
public class Client {
private static Creator creator;
private static Product product;
public static void main(String[] args) {
creator = new ConcreteCreator();
product = creator.createProduct(ConcreteProduct1.class);
/*
* 继续业务处理
*/
}
}
2.2、 抽象化分析方法
定义一个创建产品对象的工厂接口,让子类具体工厂决定实例化哪一种实例对象也就是将实际创建实例对象的工作推迟到子类当中,核心工厂类不再负责具体产品的创建。
2.3、 工厂方法模式的静态建模
对工厂方法模式进行静态化建模。通过静态类图加深对工厂方法模式的理解
(1)抽象工厂;Creator
(2)抽象产品;Product
(3)具体工厂;ConcreteCreator
(4)具体产品。ConcreteProduct
工厂方法模式中的各个角色
创建产品抽象工厂;Creator
抽象产品;Product
具体工厂生产具体产品;ConcreteCreator
具体产品。ConcreteProduct
Creator抽象工厂中应该含有一个生产抽象产品Product的接口方法,如createProduct,该方法的返回值类型是抽象产品Product类型。抽象产品Product中应该含有共有属性方法和抽象方法。ConcreteCreator具体工厂就是负责生产具体产品ConcreteProduct的,它实现了Creator抽象工厂接口方法createProduct,返回具体类型ConcreteProduct的。
具体产品ConcreteProduct就是抽象产品Product的子类
在这一系列的变化过程中,应用的是程序设计语言中的多态方式,使用接口调用对象实例的具体方法,获得不同对象的内容,而对外部是表现一致的(相同的方法),这也是面向接口编程的优点。像这样不在工厂中产生具体的对象实例,而是将实例化的工作推迟到工厂子类中,这种模式被称做工厂方法模式。
抽象工厂生产抽象产品,具体工厂生产具体产品;具体工厂实现抽象工厂接口,具体产品是抽象产品的一个子类。这也就是工厂方法设计模式!
3、 工厂方法模式的应用
3.1、工厂方法模式的优点
首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。
其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。例如在我们的例子中,需要增加一个产品,则只需要增加一个具体产品类,工厂类不用任何修改就可完成系统扩展。
再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。
在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
最后,工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
3.2、 工厂方法模式的使用场景
首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。
再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。最后,可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。
4、 设计原则
4.1、“开-闭”原则
首先了解一个软件设计的原则—“开-闭”原则。所谓“开-闭”原则,就是指一个软件实体应对扩展开放,对修改关闭。它所阐述的意思就是,在设计一个软件模块的时候应该使这个模块可以在不被修改的前提下被扩展。“开-闭”原则具备以下优势:
(1)通过已有系统扩展自身的行为,从而满足新的软件需求,具有一定的适应性和灵活性。
(2)原有的软件系统逻辑不被修改,保证了原系统和新系统的稳定性。
工厂方法模式很好地阐述了“开-闭”原则,工厂方法模式的抽象,让子类决定实例化哪一种实例对象,具有很大的适应性和灵活性;
具体产品对象是在子类工厂中产生的,没有修改任何父类工厂的代码,因此保证了系统的稳定性。工厂方法模式,就很好地体现了“开-闭”原则,这也是工厂方法设计模式的魅力所在!正是将创建具体对象实例的工作推迟到了子类工厂中实现,所以才有效地将工厂和具体类型解耦。需要新的具体产品类型,就需要一个与之相应的具体工厂来生产这个具体产品。
工厂方法设计模式在保证系统稳定的前提下,很容易实现系统扩展,灵活有效地应对外部需求的变化。
“开-闭”原则也是软件设计开发中必须考虑的设计原则,它的设计理念是每一个软件开发者必须掌握的,读者一定要认真地领悟、体会这项设计原则,做到灵活运用。
对工厂方法设计模式的学习,起到一个启迪的作用,使软件系统的结构设计更加具有弹性!
4.2、依赖倒置原则
所谓依赖倒置原则,就是不论工厂还是产品都应该依赖于抽象,而不是具体的实现类。
听起来更像是“针对接口编程,而不是针对实现编程”,但是这里依赖倒置原则更强调“抽象”的概念,不要让高层组件依赖低层组件,更不能依赖具体实现类,都要依赖于抽象。
可能会有读者对于“倒置”的概念很模糊,什么是“倒置”呢?依赖倒置原则中的倒置是指我们的思想要和一般的OO设计思想相反。通常设计一个系统,都是从顶端开始,然后到具体实现类,例如:在工厂中要生产一个产品,但是又不想在工厂中与具体的实现类存在任何关系,否则就对实现类产生了依赖,这是我们不希望得到的结果,此时我们就需要把思想倒置一下,不要从顶端(抽象)开始,而是从具体的实现类开始,看看能抽象出什么,然后一切都依赖于抽象来进行,这样就与我们的目标越来越近了。
在面向过程的软件设计开发中,往往是高层组件调用低层组件,这样的话,高层组件就会依赖于低层组件,当低层组件发生剧烈变动时,高层组件也要跟着变动,就会导致模块的复用性大大降低,并且大大提高开发的成本,也增加了软件维护的复杂度。
如图所示,为面向过程软件设计开发的调用结构图。
图为面向过程软件设计开发的调用结构图从图中可以看出,高层组件调用低层组件,即高层组件依赖于低层组件,当低层组件发生变化时,势必会对高层组件产生影响。如何改变这种状况呢?
面向对象的软件设计开发模式很好地解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度,如图所示。
图2-8 面向对象软件设计模式——依赖抽象这样一来,无论低层组件怎样变化,只要抽象组件不发生改变,高层组件就不会发生变化,实现了客户程序与实现细节的解耦。
我们可以这样理解工厂方法设计模式:(1)抽象工厂生产抽象产品,具体工厂生产具体产品;(2)抽象对具体(具体类实现抽象类)。
掌握以上这样的几个设计原则,我们在软件设计中就可以得心应手地使用工厂方法模式了。
5、 使用场合
(1)当子类型可能会有很多,以后需要不断增添不同的子类实现时;
(2)当一个系统尚在框架设计阶段,还不知道将来需要实例化哪些具体类时;
(3)系统设计之初不需要具体对象的概念(或者说没有具体对象的概念)。
以上几种情况比较适合工厂方法设计模式。通过子类来创造对象,工厂方法模式在此过程中负责将客户端从具体类型中解耦,客户端只需要知道他们所使用对象的抽象类型就可以了,而不必关心具体的对象类型,具体的对象类型由工厂子类负责创建。工厂方法设计模式如图所示。
工厂方法模式通过继承的方式实现应用程序的解耦,让子类决定实例化哪一个具体类型。如果在软件系统设计中,你发现可能会有很多的具体产品产生,但是目前又处于一种不确定的因素,这时使用工厂方法模式应该是最好的选择。这样,你就可以使用上面的工厂方法模式类图实现你的软件系统,让你的系统更加灵活、易于扩展!
在进行软件系统设计的时候,我们需要用战略的眼光来看待问题,所谓战略眼光就是需要把眼光放长远,不要只局限于目前的系统结构,要考虑到以后的若干情况,如子类型是不是会有很多、如何对系统进行扩展等。对于一个系统分析师或者一个软件设计开发人员来说,一定要把握一个原则,那就是把软件系统做成一个易扩展的、能够适应变化的系统,因为所有的软件系统都不是一成不变的,如何让我们的软件系统适应需求的变化,是我们在进行软件设计时需要思考的问题。
扩展:Java SDK中的工厂方法模式工厂方法设计模式在很多场合都有应用,使用得非常广泛,如在Java SDK中就存在工厂方法设计模式的影子。例如:java.util.List、java.util.LinkedList、java.util.ArrayList就应用了工厂方法设计模式。java.util.List作为一个抽象接口工厂,其中定义了一个iterator()接口方法,该方法返回一个迭代器接口Iterator(抽象产品),用于循环遍历List中的各个对象。子类java.util.LinkedList实现了java.util.List接口方法iterator(),返回具体的迭代器类型AbstractListItr(具体产品), Itr类也是作为AbstractList的内部私有类使用,如图2-10所示。
6、要点
1.工厂方法模式
定义一个创建产品对象的工厂接口,让子类决定实例化哪一种实例对象,也就是将实际创建实例对象的工作推迟到子类当中,核心工厂类不再负责具体产品的创建。
2.设计原则
(1)“开-闭”原则:一个软件实体应对扩展开放,对修改关闭。我们在设计软件模块的时候应该使这个模块可以在不被修改的前提下被扩展。
(2) 依赖倒置原则:不论工厂还是产品都应该依赖于抽象,而不是具体的实现类。
3.工厂方法模式的使用场合
(1)当子类型可能会有很多,以后需要不断增添不同的子类实现时;
(2)当一个系统尚在框架设计阶段,还不知道将来需要实例化哪些具体类时;
(3)系统设计之初不需要具体对象的概念(或者说没有具体对象的概念)。
7、最佳实践
工厂方法模式在项目中使用得非常频繁,以至于很多代码中都包含工厂方法模式。该模式几乎尽人皆知,但不是每个人都能用得好。熟能生巧,熟练掌握该模式,多思考工厂方法如何应用,而且工厂方法模式还可以与其他模式混合使用(例如模板方法模式、单例模式、原型模式等),变化出无穷的优秀设计,这也正是软件设计和开发的乐趣所在。