目录
- 直接方式
- 简单工厂(Simple Factory)
- 意图
- 类图
- 实现
- 总结
- 工厂方法模式(Factory Method)
- 意图
- 类图
- 实现
- 总结
- 抽象工厂模式(Abstract Factory)
- 意图
- 类图
- 实现
- 总结
- 工厂模式整体总结
- 附录
软件开发中需求是必然的,可以思考如何实现下面的这个需求:
- 披萨的种类很多(比如:GreekPizz、CheesePizz 等)
- 披萨的制作有 prepare、bake、cut、box 四个过程
- 完成一个披萨店订购功能
直接方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class OrderPizza { Pizza pizza = null;
public OrderPizza(String type) { if ("greek".equals(type)) { pizza = new GreekPizza(); } else if ("cheese".equals(type)) { pizza = new CheessPizza(); } else if ("china".equals(type)) { pizza = new ChinaPizza(); } assert pizza != null; pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } }
|
使用这种方式能够直观的完成需求,简单易编码。然而,这种编码方式违反了 OCP 原则,即对扩展开放,对修改关闭。一旦,当我们给类增加新功能的时候,尽量不修改原代码,或者尽可能少的修改代码。
如果此时需要新增加一个Pizza
的种类,就需要再一次的增加 else-if
判断,这种修改代码是可以的接受的,但是如果创建 Pizza
实例的地方不仅仅是在 OrderPizza
的构造函数中,在其他地方也有创建 Pizza
的代码,这就意味着,所有创建实例的地方,都有可能修改
🎯 修改代码可以接受,但是如果我们在其他的地方也有创建 Pizza
的代码,就意味着,后续需求的变更这些创建的地方也需要更改,那么减少这种创建的实例的地方,就能够很好的减少后续需求的改动!
💡 根据上面的思路,将创建 Pizza
对象的过程封装到一个类中,这样后续有新的需求变更时,只需要更改此类就可以了,这种想法的实现就是:简单工程模式
简单工厂(Simple Factory)
简单工厂( Simple Factory),并不能算是一种设计模式,但是大家都在用,所以默认简单工厂是一种模式。
它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化,这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式。
意图
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口
类图
简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
组成(角色) | 关系 | 作用 |
---|
抽象产品(Product) | 具体产品的父类 | 描述产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
工厂(Factory) | 被外界调用 | 根据传入参数从而创建不同具体产品类的实例 |
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class PizzaSimpleFactory { private PizzaSimpleFactory() { }
public static Pizza createPizza(String type) { Pizza pizza = null; if ("greek".equals(type)) { pizza = new GreekPizza(); } else if ("cheese".equals(type)) { pizza = new CheessPizza(); } else if ("china".equals(type)) { pizza = new ChinaPizza(); } assert pizza != null; pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }
|
后续只需要使用 PizzaSimpleFactory
提供的 createPizza
接口就可以获得对应的 pizza
实例,并且后续增加新的类型,也只需要修改这个接口中的内容即可
总结
👍 使用简单工厂有如下的优点:
- 能够将创建实例工作与使用实例的工作分开,使用者不必关心类对象如何创建,实现了解耦;
- 把初始化实例时的工作放到工厂里进行,使代码更容易维护。 更符合面向对象的原则 & 面向接口编程,而不是面向实现编程
😣 使用简单工厂模式具有如下的缺点:
- 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
- 违背「 开放 - 关闭原则 」,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂
- 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构
简单工程模式的应用场景如下:
- 客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时
- 当工厂类负责创建的对象(具体产品)比较少时
工厂方法模式(Factory Method)
简单工厂模式存在一系列问题,为了解决上述的问题,就设计出了一种新的模式:工厂方法模式。工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,工厂父类定义需要创建对象的接口,而具体的子类则负责生成具体的对象
意图
✨ 工厂方法模式将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化哪一个类
🎯 主要目的是为了解决简单工厂模式中存在的:工厂一旦需要生产新产品就需要修改工厂类的方法逻辑,违背了「 开放 - 关闭原则 」
之所以工厂方法模式可以解决简答工厂的问题,是因为工厂方法模式把具体产品的创建推迟到工厂类的子类(具体工厂)中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式在添加新产品的时候就不修改工厂类逻辑而是添加新的工厂子类,符合开放封闭原则,克服了简单工厂模式中缺点
类图
组成(角色) | 关系 | 作用 |
---|
抽象产品(Product) | 具体产品的父类 | 描述具体产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
抽象工厂(Factory) | 具体工厂的父类 | 描述具体工厂的公共接口 |
具体工厂(Concrete Factory) | 抽象工厂的子类;被外界调用 | 描述具体工厂;实现FactoryMethod 工厂方法创建产品的实例 |
实现
1 2 3 4 5 6 7 8 9 10 11 12
| public abstract class Factory { public void doSomething() { Pizza pizza = factoryMethod(); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }
abstract public Pizza factoryMethod(); }
|
1 2 3 4 5 6
| public class CheesePizzaFactory extends Factory { @Override public Pizza factoryMethod() { return new CheessPizza(); } }
|
1 2 3 4 5 6
| public class ChinaPizzaFactory extends Factory { @Override public Pizza factoryMethod() { return new ChinaPizza(); } }
|
1 2 3 4 5 6 7 8
| public class FactoryMethodTest { public static void main(String[] args) { Factory factory = new ChinaPizzaFactory(); factory.doSomething(); factory = new CheesePizzaFactory(); factory.doSomething(); } }
|
当新增需求时,只需要新增加一个具体商品类,继承抽象商品类;随后再增加一个具体的工厂子类,继承自抽象工厂;就可以使用多态的技术,得到新增加的商品
总结
👍 工厂方法模式的优点
- 更符合开-闭原则,新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可;简单工厂模式需要修改工厂类的判断逻辑
- 符合单一职责原则,每个具体工厂类只负责创建对应的产品;简单工厂中的工厂类存在复杂的 switch 逻辑判断
- 不使用静态工厂方法,可以形成基于继承的等级结构;简单工厂模式的工厂类使用静态工厂方法
😣 工厂方法模式的缺点
- 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到 DOM、反射等技术,增加了系统的实现难度
- 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类
- 一个具体工厂只能创建一种具体产品
🎶 工厂模式可以说是简单工厂模式的进一步抽象和拓展,在保留了简单工厂的封装优点的同时,让扩展变得简单,让继承变得可行,增加了多态性的体现
工程方法模式的应用场景:
当一个类不知道它所需要的对象的类时。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可;
当一个类希望通过其子类来指定创建对象时。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展
将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
抽象工厂模式(Abstract Factory)
工厂方法模式存在一个严重的问题:一个具体工厂智能创建一类产品;而在实际过程中,一个工程可能需要生产多类产品,为了解决上述的问题,又设计了一种新的设计模式:抽象工厂模式
意图
抽象工厂模式,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。
🆚 抽象工厂模式与工厂方法模式最大的区别:抽象工厂中每个工厂可以创建多种类的产品;而工厂方法每个工厂只能创建一类
允许使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么,这样就可以从具体产品中被解耦
🎯 解决的问题:工厂方法模式中每个工厂只能创建一类产品,抽象工厂模式将创建一系列的对象家族,并且这些对象是相关的
类图
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。
创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。从高层次来看,抽象工厂使用了组合,即 Client 组合了 AbstractFactory
,而工厂方法模式使用了继承
组成(角色) | 关系 | 作用 |
---|
抽象产品族(Client) | 抽象产品的父类 | 描述抽象产品的公共接口 |
抽象产品(AbstractProduct) | 具体产品的父类 | 描述具体产品的公共接口 |
具体产品(Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
抽象工厂(AbstactFactory) | 具体工厂的父类 | 描述具体工厂的公共接口 |
具体工厂(ConcreteFactory) | 抽象工程的子类;被外界调用 | 描述具体工厂;实现 createProduct() 工厂方法创建产品的实例 |
其中抽象产品族、抽象产品和具体产品的区别如下:
实现
1 2 3 4 5
| public class AbstractProductA { } public class AbstractProductB { }
|
1 2 3 4
| public class ProductA1 extends AbstractProductA { } public class ProductA2 extends AbstractProductA { }
|
1 2 3 4
| public class ProductB1 extends AbstractProductB { } public class ProductB2 extends AbstractProductB { }
|
1 2 3 4
| public abstract class AbstractFactory { abstract AbstractProductA createProductA(); abstract AbstractProductB createProductB(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class ConcreteFactory1 extends AbstractFactory { AbstractProductA createProductA() { return new ProductA1(); }
AbstractProductB createProductB() { return new ProductB1(); } } public class ConcreteFactory2 extends AbstractFactory { AbstractProductA createProductA() { return new ProductA2(); }
AbstractProductB createProductB() { return new ProductB2(); } }
|
1 2 3 4 5 6 7 8
| public class Client { public static void main(String[] args) { AbstractFactory abstractFactory = new ConcreteFactory1(); AbstractProductA productA = abstractFactory.createProductA(); AbstractProductB productB = abstractFactory.createProductB(); } }
|
总结
👍 抽象工厂模式的优点:
- 降低耦合,抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展;
- 更符合开-闭原则,新增一种产品类时,只需要增加相应的具体产品类和相应的工厂子类即可
- 符合单一职责原则,每个具体工厂类只负责创建对应的产品
- 不使用静态工厂方法,可以形成基于继承的等级结构
😣 抽象工厂模式的缺点
抽象工厂模式很难支持新种类产品的变化,这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的所有子类的改变,这样也就违背了「 开闭原则」,这是一种对于新的产品族符合开闭原则,对于新的产品种类不符合开闭原则,这一特性也称之为开闭原则的倾斜性
🎶 工厂方法模式的应用场景如下:
- 这个系统有多个系列产品,而系统中只消费其中一系列产品
- 系统要去提供一个产品类的库,所有产品以同样的接口出现,客户端不需要具体实现
工厂模式整体总结
并不是说一路总结下来,抽象工厂模式最好,其他模式都不好,还是需要按照具体的应用场景进行分析。只有最合适某些场景的模式,没有最好的模式
附录
简单工厂
创建型 - 抽象工厂(Abstract Factory)
Carson 带你学设计模式:简单工厂模式(SimpleFactoryPattern)
Carson 带你学设计模式:工厂方法模式(Factory Method)
Carson 带你学设计模式:抽象工厂模式(Abstract Factory)