什么是工厂模式
在我们日常编码中,到处都遍布着new操作。当看到new的时候,可能你就会想到这是一个”具体”了,那么new有什么不对劲吗?从设计原则我们可以知道,我们的代码应该“对扩展开放,对修改关闭(Open Close Priciple)”。当针对具体来编程时,我们的老朋友“改变”也就会经常来捣乱了,因为一旦加入新的具体类型的时候,就必须改变代码,也就是说,我们的代码,并非“对修改关闭”,想用新的具体类型的时候,必须重新打开它。
而这,就是工厂模式可以解决的问题。将实例化具体类型的代码从应用中抽离出来,或者封装起来,使它们不会干扰应用的其他部分。
简单工厂模式(Simple Factory Pattern)
简单工厂 = 具体的工厂 + 抽象的产品 + 具体的产品。简单来说,简单工厂就是,用一个具体的工厂来生产不同的产品。因为用了具体的工厂,所以也有些人认为它不是一种设计模式,而是一种常见的编程习惯。
模式定义
在简单工厂中, 根据参数的不同返回不同的对象。专门创建了一个类来负责创建其他类的对象,被创建的对象通常拥有共同的父类。
模式结构
简单工厂包含如下角色:
- Factory: 工厂
- 负责创建所有具体产品的逻辑处理;
- Product: 抽象产品
- 是工厂创建的所有对象的父类,负责描述所有对象的公共接口;
- ConcreteProduct: 具体产品
- 工厂创建的目标;
类图
优点
- 工厂类有必要的判断逻辑,可以决定什么时候创建哪一个具体对象。客户端免除了对创建产品对象的责任,而仅仅是消费产品。实现了对责任的分割,提供了专门的工厂类用于创建对象;
- 可以在不修改客户端代码的情况下添加新的产品,在一定程度上提高了系统的灵活性;
缺点
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,违背了开闭原则,在产品类型较多的时候,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护;
- 由于工厂集中了所有产品的创建逻辑,一旦不能正常工作,整个系统都会受到影响;
- 使用了静态的工厂方法,造成工程角色无法形成基于继承的等级结构;
应用场景
以下场景比较适合使用简单工厂模式:
- 工厂类创建的产品比较少时:因为如果产品类型太多,会造成工厂方法的逻辑过于复杂;
- 客户端对如何创建对象不关心,甚至连类名都不需要知道,只知道类型所对相应的参数;
简单工厂的使用
|
|
|
|
|
|
|
|
|
|
在JDK中的应用
JDK类库中广泛使用了简单工厂模式,比如
java.text.DateFormat
,它用于格式化一个本地日期或时间。123java.text.DateFormat#getDateInstance()java.text.DateFormat#getDateInstance(int)java.text.DateFormat#getDateInstance(int, java.util.Locale)Java加密技术
获取不同加密算法的密钥生成器:1KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
创建密码器
工厂方法模式(Factory Method Pattern)
工厂方法 = 抽象的工厂 + 具体的工厂 + 抽象的产品 + 具体的产品。在简单工厂中,工厂是一个具体类,而把这个工厂也抽象一层,就变为了工厂方法模式。
模式定义
工厂方法模式,又称为工厂模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做得目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪个具体产品类。
模式结构
工厂方法模式包含如下角色:
- Pruduct:抽象产品
- ConcreteProduct:具体产品
- Factory:抽象工厂
- ConcreteFactory:具体工厂,每个具体工厂只对应一种具体产品
类图
优点
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同事还向客户隐藏了哪种具体产品类将被实例化这个细节,客户只需要关心所需产品对应的工厂,无需关心细节;
- 基于工厂角色和产品角色的多态性设计师工厂方法模式的关键,它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节完全被封装到具体工厂内部。工厂方法模式所以又被称为多态工厂模式,是因为具体工厂类都具有同一个抽象工厂的父类;
- 使用工厂方法模式的另一个优点是在系统中加入新的产品时,无需修改抽象工厂和抽象产品的接口,无需修改客户端,也无需修改其他具体的工厂和具体的产品,而只要添加一个具体工厂和相对应的具体产品就可以了。这样,系统的扩展性也就变得非常好,完全符合开闭原则;
缺点
- 添加新产品的时候,需要编写新的具体工厂和具体产品,系统中类的个数将成对增加,在一定程度上增加了系统的复杂性,有更多的类需要编译和运行,会给系统带来一些额外的开销;
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度;
使用场景
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂创建,客户端只需要知道具体产品的工厂类;
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里式替换原则,在程序运行的时候,子类对象将覆盖父类对象,从而使系统更容易扩展;
- 将创建对象的任务委托给多个工厂子类中的一个,客户端在使用时可以无需关心哪个工厂子类创建产品子类,需要时再动态指定,可以将具体工厂的名字存储在配置文件或者DB中;
模式扩展
- 使用多个工厂方法:在抽象工厂角色中可以定义多个工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以包含不同的业务逻辑,以满足不同产品对象的需求;
- 产品对象的重复使用:工厂对象将已经创建过的产品保存到一个集合中,然后根据客户端对产品的请求,对集合进行查询,如果有满足条件的产品对象,就直接将这个产品对象返回给客户端;如果没有,就创建一个新的产品对象,并把它放入到集合中,再返回给客户端;
- 多态性的丧失和模式的退化:如果工厂仅仅返回一个具体产品对象,就违背了工厂方法的用意。发生退化,此时就不再是工厂方法模式了。一般来说,工厂对象应当有一个抽象的父类型,如果工厂等级结构中只有一个具体工厂的话的,抽象工厂就可以省略了,也就发生了退化。当只有一个具体工厂时,在具体工厂中可以创建所有的产品对象,并且工厂方法设计为静态方法时,工厂方法模式就退化成简单工厂模式了。
工厂方法模式的使用
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在JDK中的应用
Collection中的iterator
java.util.Collection
中的Iterator<E> iterator()
方法就是一个工厂方法,对于iterator()来说,java.util.Collection就是一个抽象工厂,再往下还有java.util.List
等作为二级抽象工厂,继续往下就是java.util.ArrayList
等具体工厂了。
java.util.Iterator
接口是抽象产品,下面还有java.util.ListIterator
二级抽象产品,往下有java.util.ArrayList.ListItr
等具体产品。
JDBC的Connection
JDBC是JDK提供的标准化数据接口,各大数据厂商都实现了JDBC API,JDBC是使用工厂方法模式,我们关注1个类和3个接口:
java.sql.Driver
:每个数据库厂商都要实现这个接口,用来注册驱动,打开数据库连接;java.sql.Connection
:每个数据库厂商都是实现这个接口,用来执行数据操作;java.sql.DriverManager
:JDK提供的用来管理Driver的类;
我们来看一个网上的图:
- Driver接口就是模式中的抽象工厂,里面的
Connection connect(String url, java.util.Properties info)
就是工厂方法。下面的MysqlDriver和OracleDriver类就是具体工厂。而Connection接口就是抽象产品类,下面的MysqlConnectionImpl和OracleConnectionImpl就是具体产品。(备注,mysql和oracle中,实际的类名可能不是这样) - 客户端调用不依赖于具体工厂和产品(即究竟是mysql驱动还是oracle驱动,客户端不用关心,客户端只使用抽象的Driver和Connection),而是依赖抽象工厂和抽象产品进行工作;
在Spring中的应用
在Spring中,我们通常会利用XML或者annotation的方式来配置bean。在利用XML配置的时候,有以下3中方式:
- 反射方式
- 工厂方式(静态工厂+工厂方法)
- Factory Bean方式
反射方式
|
|
|
|
反射模式是最常见的,配置bean的时候需要声明class属性为类路径,Spring会利用Java的反射技术来创建这个bean。
静态工厂模式(简单工厂)
在简单工厂模式中,Spring不会直接利用反射技术来创建bean,而是会利用反射技术先找到工厂类,然后利用工厂类来创建bean。所谓静态工厂模式,就是指工厂类不需要实例化,这个工厂类提供了一个静态的方法来创建bean。
|
|
可以看出,利用静态工厂方法定义的bean,class属性不是bean的类路径,而是静态工厂的类路径,而且还需要指定工厂里面getBean的静态方法和参数。
实例工厂方式(工厂方法模式)
实例工厂方式与上面静态工厂方式的区别是,getBean不是静态的,也就是说要先实例一个工厂对象,才能依靠这个工厂对象创建bean。
|
|
|
|
静态工厂是把数据写死在class里面,以后要增加类型就只能修改代码,不符合开闭原则。而利用实例工厂,可以通过修改配置文件的方式来修改工厂里面的数据,符合开闭原则。
Factory Bean方式
Factory Bean方式与工厂方法有点类似,我们同样需要一个工厂类,只不过Spring提供了一个FactoryBean接口,我们的工厂类必须实现这个接口。
|
|
|
|
可以看出,bean的配置和反射机制非常类似,但是利用反射配置的bean,class的值就是bean的类路径,而利用FactoryBean方式,class属性的值是工厂类的类路径。一旦这个工厂类实现了FactoryBean接口,那么这个bean返回的就是它里面getObject方法返回的对象。
抽象工厂模式(Abstract Factory Pattern)
抽象工厂 = 抽象的工厂 + 具体的工厂 + 抽象的产品 + 具体的产品 + 产品关系。抽象工厂和工厂方法最大的区别,我的理解就是抽象工厂下的具体工厂可以生产一批不同类的产品,规范点来说就是生产整个产品家族。而工厂方法只是生产一批同类的产品。
模式动机
- 在工厂方式模式中具体工厂负责生产具体的产品,每个具体工厂对应生产一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。
- 为了更清晰理解抽象工厂模式,需要先引入两个概念:
- 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、TCL电视机、SONY电视机等,则抽象电视机与具体品牌的电视机之间就构成了一个产品等级结构,抽象电视机是父类,具体品牌电视机是子类;
- 产品族:在抽象工厂模式中,产品族是指由一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔洗衣机、海尔电冰箱等,海尔电视机位于电视机产品等级结构中,海尔电冰箱则位于电冰箱产品等级结构中。
- 为了更清晰理解抽象工厂模式,需要先引入两个概念:
- 当系统所提供的工厂所需生成的具体产品并不是一个简单的对象,而是位于多个不同产品等级结构中不同类型的具体产品时,就需要用到抽象工厂模式;
- 抽象工厂模式是所有形式的工厂模式中最为抽象和最具有一般性的一种形态;
- 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要创建多个产品结构。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族的所有对象时,抽象工厂模式比工厂方法模式更为简单、高效;
模式定义
抽象工厂模式:提供一个创建一系列相关或者相互依赖对象的结构,而无需指定它们具体的类。又称为Kit模式。
模式结构
抽象工厂模式包含如下角色:
- Factory:抽象工厂
- ConcreteFactory:具体工厂
- AbstractProduct:抽象产品
- ConcreteProduct:具体产品
类图
优点
- 抽象工厂模式隔离了具体类的生成,使得客户端并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到广泛的应用;
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常使用的设计模式;
- 增加新的工具工厂和产品族很方便,无需修改已有系统,符合开闭原则;
缺点
- 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品意味着要对抽象工厂接口进行修改,而这将设计到抽象工厂以及其所有子类的修改,显然会带来很大的不便;
- 开闭原则的倾斜性(增加新的工厂和新的产品族很容易,增加新的产品等级结构很麻烦);
使用场景
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的;
- 系统中有多个产品族,而每次只使用其中一个产品族;
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统设计中提现出来;
- 系统提供一个产品类的库,所有的产品都已同样的接口出现,从而使客户端不依赖具体实现;
模式扩展
“开闭原则”的倾斜性
“开闭原则”要求系统对扩展开放、对修改关闭,通过扩展达到增强其功能的目的。对于涉及到多个产品族和多个产品等级结构的系统,其功能增强主要包括2方面:
- 增加产品族:对于增加产品族,抽象方法模式很好的支持了“开闭原则”,对于要增加新的产品族,只需要对应增加一个新的具体工厂集合,对已有代码无需做任何修改。
- 增加新的产品等级机构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂,在所有的工厂类中都需要增加生产新产品的方法,不能很好地支持“开闭原则”;
抽象工厂模式的这种性质称为“开闭原则”的倾斜性,已一种倾斜的方式支持增加新的产品族,它为增加新的产品族提供了方便,但不能为新的产品等级结构的增加提供这样的便利。
抽象工厂模式的退化
当抽象工厂模式每个具体工厂类都只生产一个产品对象,也就是指存在一个产品等级结构时,抽象工厂模式退化为工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建对象,并将创建对象的方法设计为静态方法的时候,工厂方法模式退化成简单工厂模式。
抽象工厂方法的使用
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在JDK中的应用
在java.sql包中,有个典型的抽象工厂模式的应用。我们看一副来自网上的类图:
java.sql.Connection
:抽象工厂类,createStatement、prepareStatement、prepareCall这三个方法分别生产Statement、PreparedStatement、CallableStatement,就是说明产品族是3个;java.sql.Statement
/java.sql.PreparedStatement
/java.sql.CallableStatement
:抽象产品;MysqlCollectionImpl,实际是com.mysql.jdbc.ConnectionImpl
:具体工厂;StatementImpl
/PreparedStatementImpl(实际是com.mysql.jdbc.PreparedStatement)
/CallableStatementImpl(实际是com.mysql.jdbc.CallableStatement)
:具体产品;