写在前面
工厂模式实际上包含
简单工厂
、
工厂方法
、
抽象工厂
三种设计模式,其中
简单工厂
模式并不在 GOF23 种设计模式中,由于其使用频率较高,也通常被称为一种入门的设计模式。本篇将会不惜篇幅循序渐进展开三种设计模式之间的关联和推进,达到更好的理解和使用。
而三种工厂模式设计目的均是为了解决对象的创建问题,所以工厂模式也属于创建型模式。在不使用工厂模式时,我们的代码通过将对象的创建和使用写在一处,这种做法将违反
单一职责
和
开闭原则
,不利于代码的重用和维护,稍后我们会详细解释。
简单工厂模式
什么是简单工厂模式
简单工厂模式,也叫静态工厂方法,是由一个工厂对象决定创建出哪一种产品类的实例
即我们把原来跟对象的使用耦合在一起的代码提炼出一个专门负责创建对象的工厂,转而通过调用工厂方法来得到对象。
为什么要用简单工厂模式
让我们来看定一份披萨通常是什么样:
//披萨店类
Public class PizzaStore{
Public Pizza orderPizza(String type){
Pizza pizza = NULL;
if(type.equals("chicken")){
pizza = new ChickenPizza();
}esle if(type.equals("vegetables")){
pizza = new VegetablesPizza();
}else{
//pizza = ...
}
//还可能对pizza对象做一些初始化工作
//执行切片、打包等过程
pizza.cut();
pizza.box;
return pizza;
}
}
在这里我们将
Pizza
对象的创建和使用耦合在一起,这样有什么坏处呢?首先便是违反了
单一职责
,一个方法没有只做一件事,而且很有可能其他地方也需要用到
Pizza
对象的创建和初始化,不利于代码的复用;其次,如果我们需要添加新的
Pizza
种类,或者需要更改初始化代码,那么就需要在
if...else
中不断更改,违反了
开闭原则
。
怎么用简单工厂模式
而如果我们将对象的创建和初始化代码封装在工厂方法中,需要的时候我们直接调用工厂方法获取生产出的对象实例。一旦需要增改,只需要在工厂方法中更改一份,其他用到对象创建的地方就都生效了。 就像这样:
//披萨店类
Public class PizzaStore{
SimpleFactory simpleFactory;
Public PizzaStore(SimpleFactory simpleFactory){
this.simpleFactory = simpleFactory;
}
Public Pizza orderPizza(String type){
Pizza pizza = NULL;
pizza = simpleFactory.createPizza(type);
//执行切片、打包等过程
pizza.cut();
pizza.box();
return pizza;
}
}
//披萨工厂类
Public class SimpleFactory{
//通过这个方法得当Pizza对象实例
Public Pizza createPizza(String type){
Pizza pizza = NULL;
if(type.equals("chicken")){
pizza = new ChickenPizza();
}esle if(type.equals("vegetables")){
pizza = new VegetablesPizza();
}else{
//pizza = ...
}
//还可能对pizza对象做一些初始化工作
return pizza;
}
}
这样一来,定一份披萨无需关心披萨如何被生产的,只需要告诉工厂要哪种披萨,甚至后期可通过
XML
描述文件来进行软编码,达到不用重新编译即可新增披萨种类。这其实很类似于
Spring
框架的
IOC
原理,你只需要描述对象是如何创建的,通过注解或
XML
形式配置,使用时传入配置类名对应的简称,
IOC
容器就会通过
BeanFactory
创建并返回对应的实例。但其实
IOC
的实现更像是下面介绍的 工厂方法模式,因为它还有多个具体工厂。
在实际开发中,我们还可以视情况选择在工厂中利用反射达到对象的创建,从而避免增改工厂方法中
if...else
逻辑代码。
工厂方法模式
什么是工厂方法模式
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
为什么要用工厂方法模式
简单工厂模式中我们只有一个工厂类,用来生成产品(对象实例),但是当新增产品时,不可避免的要去修改工厂类中生成产品的代码,即上文的
if...else
逻辑判断代码,这实际上违背了
开闭原则
。
而工厂方法则是提取抽象工厂类,并增加了具体工厂实现类,来实现当新增产品时,无需修改原先封装的抽象工厂类,只需要新加一个生产该产品的具体工厂实现类。
怎么用工厂方法模式
//披萨店工厂抽象类
Public abstract class PizzaStore{
//该方法提供给外界使用,通过让外界直接调用业务方法,隐藏工厂方法
Public final Pizza orderPizza(String type){
Pizza pizza = NULL;
pizza = createPizza(type);
//执行切片、打包等过程
pizza.cut();
pizza.box;
return pizza;
}
//通过这个方法得当Pizza对象实例
abstract Pizza createPizza(String type);
}
//披萨工厂具体实现类1
Public class ConcreateStore1 extends PizzaStore{
Public Pizza createPizza(String type){
Pizza pizza = NULL;
if(type.equals("chicken")){
pizza = new ChickenPizza();
}else{
//pizza = ...
}
//还可能对pizza对象做一些初始化工作
return pizza;
}
}
//披萨工厂具体实现类2
Public class ConcreateStore2 extends PizzaStore{
public Pizza createPizza(String type){
Pizza pizza = NULL;
if(type.equals("vegetables")){
pizza = new VegetablesPizza();
else{
//pizza = ...
}
//还可能对pizza对象做一些初始化工作
return pizza;
}
}
//模拟客户端调用
public static void main(String args[]) {
PizzaStore pizzaStore = new ConcreateStore1();//可引入配置文件实现
Pizza pizza = pizzaStore.orderPizza("chicken");
pizza.eat();//这里就是 Pizza 类提供的方法了,如吃披萨
}
代码中的
PizzaStore
就是工厂抽象类,你也可以把这个类名改为
PizzaFactory
,把其工厂实现类如
ConcreateStore1
改为
ConcreatePizzaFactory1
。
并且我们在抽象类
PizzaStore
中加了一个
orderPizza
的业务方法,并设为不可变防止子类重写,在这个方法里调用
createPizza
实现
Pizza
对象的创建和初始化。一次来对客户端进行隐藏,客户端只要调用
orderPizza
这个业务方法即可。当然,你也可以不要这个方法,直接调用
createPizza
方法。
由于我们是针对接口编程(接口可范指 Java 中的接口和抽象类),而不是对具体的实现类。所以我们总是可以通过调用接口的
orderPizza
方法来达到,并传入想要的口味来得到披萨,即便现在新增了一家“必胜客”披萨店,我只要实现
PizzaStore
的
createPizza
方法,其他都不用修改, 就能吃到必胜客的鸡肉味披萨。当然此时客户端代码第一行需要改为:
但实际上这行代码我们也可以通过读取配置文件,通过更改配置文件来实现不同工厂的热拔插。
我们可以发现工厂方法模式的一个特点,让子类(工厂具体实习类)真正实现工厂方法并创建对象,因此如果增加实现类,工厂抽象类并不知道,但客户端确是通过抽象类去调用的,从而达到将对象的使用和创建解耦。这实际上符合
依赖倒置
原则 ——
抽象不应该依赖于细节,细节应当依赖于抽象
。
依赖倒置
原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。
一般情况我们的披萨店由于需要制作不同口味披萨,会依赖于这些不同的披萨类,但是当我们对其进行倒置时,转为披萨店依赖于披萨抽象接口,而不依赖具体披萨类。而具体类的对象则是通过
依赖注入
的方式注入到依赖它的对象中去。常用的注入方式有三种,分别是:
构造注入
,
设值注入(Setter注入)
和
接口注入
。是不是觉得
工厂方法模式
很像
Spring IOC
的实现?
抽象工厂模式
什么是抽象工厂模式
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。
为什么要用抽象工厂模式
让我们引入这样一个问题:在
工厂方法模式
中,每增加一种产品就需要实现其对应的工厂,会导致增加许多类。
我们前面介绍的不同披萨工厂生产不同的风味的披萨,但都只是披萨这个单一产品。比如必胜客这个生产披萨的工厂(餐厅)如果只生产披萨就太浪费了,如果我们新加一种产品——意面,那不是得专门为其开一间必胜客意面店?但我们知道实际上必胜客的招牌不仅有披萨还有意面等等产品,这一系列必胜客制作的叫
产品族
,如果我们的
抽象工厂模式
也能像必胜客一样,能够生产相关产品族而不需要新增工厂就太好了,而实际上也确实就是这么做的。
也就是说,抽象工厂模式很适合生产一组产品,比如在开发中经常需要为一组产品提供不同的主题,此时增换主题只需要增换工厂即可。
怎么用抽象工厂模式
//工厂抽象类
Public abstract class Factory{
abstract Pizza createPizza(String type);
abstract Noodle createNoodle(String type);
}
//必胜客工厂具体实现类
Public class PizzaHutFactory extends Factory{
Public Pizza createPizza(String type){
Pizza pizza = NULL;
if(type.equals("chicken")){
pizza = new ChickenPizza();
}else{
//pizza = ...
}
//还可能对pizza对象做一些初始化工作
return pizza;
}
Public Noodle createNoodle(String type){
Noodle noodle = NULL;
if(type.equals("chicken")){
noodle = new ChickenNoodle();
}else{
//noodle = ...
}
//还可能对noodle对象做一些初始化工作
return noodle;
}
}
//必败客工厂具体实现类
Public class anotherFactory extends Factory{
public Pizza createPizza(String type){
Pizza pizza = NULL;
if(type.equals("vegetables")){
pizza = new VegetablesPizza();
else{
//pizza = ...
}
//还可能对pizza对象做一些初始化工作
return pizza;
}
Public Noodle createNoodle(String type){
Noodle noodle = NULL;
if(type.equals("vegetables")){
noodle = new ChickenNoodle();
}else{
//noodle = ...
}
//还可能对noodle对象做一些初始化工作
return noodle;
}
}
public static void main(String args[]) {
//实例化必胜客工厂(只要一个工厂就能得到一系列该工厂生成的产品)
Factory factory = new PizzaHutFactory();
Pizza pizza = factory.create("chicken");//一份必胜客鸡肉味披萨
Noodle noodle = factory.create("chicken");//一份必胜客鸡肉味意面
pizza.eat();
noodle.eat();
}
可以发现,
抽象工厂模式
在生产系列产品时很方便,不论是从工厂类的数量还是从客户端使用时的配置。如果我们要为这些产品族再新增一个主题,或者说新开一家西餐店,只要实现抽象工厂中的方法,其他什么地方均不用更改。
但是
抽象工厂模式
也有其缺点,比如想要新增一个产品,就需要更改抽象工厂,已经实现的具体工厂,还有客户端代码,
抽象工厂模式
的这种性质称为
开闭原则
的倾斜性。所以它通常更适合在需要一起使用一系列产品的时候。
总结
简单工厂模式
优点
将对象的创建和使用分离,保证方法的单一职能,利于代码复用,引入工厂做到一处修改,处处生效。
缺点
增改产品时,需要修改工厂方法里的逻辑代码,不利于维护。
适用场景
工厂类需要创建的对象数量较少。
工厂方法模式
优点
新增产品时,只需要新加一个工厂具体实现类,无需修改原有代码;客户端在适用时,无需关心创建细节,甚至是产品类名。
缺点
一个产品对应一个工厂,导致工厂数目增多,尤其在同时适用系列产品时更为明显。
适用场景
其实就是解决简单工厂中的问题,使其变得利于扩展和维护。
抽象工厂模式
优点
同时使用系列产品时,增加新的工厂很方便,客户端使用也只需创建一个工厂。
缺点
新增产品而不是产品族,需要修改大量代码。
适用场景
例如我们需要对一套UI控件(产品族)提供不同的主题,不论是新增主题还是设置主题都很方便。