设计模式—工厂模式 - 图1
基于如下几个维度来讲述:
设计模式—工厂模式 - 图2

简单工厂

业务背景介绍

在一些特定的场景中,为保证代码的归一性,需要根据具体的硬件产品的形态,芯片的结构等进行耦合。那么在代码层面就需要根据硬件产品的形态创建不同的实现类。

在MindStudio的Profiling功能中,就存在一部分功能需要跟具体的硬件耦合。需要根据不同的硬件形态,创建不同的采集指标页面。

编码实现

code

  1. public interface ConfigPage {
  2. void repaint();
  3. }
  4. public class C7xConfigPage implements ConfigPage {
  5. @Override
  6. public void repaint() {
  7. System.out.println("C7x config page");
  8. }
  9. }
  10. public class MdcConfigPage implements ConfigPage {
  11. @Override
  12. public void repaint() {
  13. System.out.println("Mdc config page");
  14. }
  15. }
  16. public class LihisConfigPage implements ConfigPage {
  17. @Override
  18. public void repaint() {
  19. System.out.println("lihis config page");
  20. }
  21. }
  22. public class Client {
  23. public static ConfigPage createConfigPage(String createType) {
  24. switch (createType) {
  25. case "C7x": return new C7xConfigPage();
  26. case "MDC": return new MdcConfigPage();
  27. case "Lihis": return new LihisConfigPage();
  28. default: return null;
  29. }
  30. }
  31. }

对应的UML图

设计模式—工厂模式 - 图3

存在的问题

  1. Client感知了ConfigPage的各种具体实现类,不符合最小知道原则;
  2. 当新增一种产品形态时,Client要同步修改;不符合开闭原则;

    使用模式后的实现

    code

    ```java public class SampleFactory { public static ConfigPage createConfigPage(String createType) {
    1. switch (createType) {
    2. case "C7x": return new C7xConfigPage();
    3. case "MDC": return new MdcConfigPage();
    4. case "Lihis": return new LihisConfigPage();
    5. default: return null;
    6. }
    } }

public class Client { public static ConfigPage createConfigPage(String createType) { return SampleFactory.createConfigPage(createType); } }

  1. 这个不就是把Client中的代码实现挪动到SampleFactory中了吗?没看出其他的区别了?别着急,从下面UML图中可以看出具体的区别。
  2. <a name="c4g4T"></a>
  3. ### UML图以及优缺点
  4. ![](https://cdn.nlark.com/yuque/__puml/5cdb97d5c9d4298fab86b42134fab4a1.svg#lake_card_v2=eyJ0eXBlIjoicHVtbCIsImNvZGUiOiJjbGFzcyBDbGllbnQge1xuXHRDb25maWdwYWdlIGNyZWF0ZUNvbmZpZ1BhZ2UoU3RyaW5nKVxufVxuXG5wYWNrYWdlIGNvbmZpZ1BhZ2Uge1xuY2xhc3MgU2FtcGxlRmFjdG9yeSB7XG5cdENvbmZpZ3BhZ2UgY3JlYXRlQ29uZmlnUGFnZShTdHJpbmcpXG59XG5cbmludGVyZmFjZSBDb25maWdQYWdlIHtcblx0dm9pZCByZXBhaW50KClcbn1cblxuY2xhc3MgQzd4Q29uZmlnUGFnZSBpbXBsZW1lbnRzIENvbmZpZ1BhZ2Uge1xuXHR2b2lkIHJlcGFpbnQoKVxufVxuXHRcbiBTYW1wbGVGYWN0b3J5IC5yaWdodC4-IENvbmZpZ1BhZ2Vcblx0U2FtcGxlRmFjdG9yeSAuLj4gQzd4Q29uZmlnUGFnZVxuIENsaWVudCAuLj4gU2FtcGxlRmFjdG9yeVxuIENsaWVudCAuLj4gQ29uZmlnUGFnZVxufSIsInVybCI6Imh0dHBzOi8vY2RuLm5sYXJrLmNvbS95dXF1ZS9fX3B1bWwvNWNkYjk3ZDVjOWQ0Mjk4ZmFiODZiNDIxMzRmYWI0YTEuc3ZnIiwiaWQiOiJldUttVSIsIm1hcmdpbiI6eyJ0b3AiOnRydWUsImJvdHRvbSI6dHJ1ZX0sImNhcmQiOiJkaWFncmFtIn0=)
  5. 1. Client不需要具体感知ConfigPage的实现了,将这部分关联放到了SampleFactory中了,对Client端而言,耦合性降低了;
  6. 1. 将所有的更改点都集中到SampleFactory中,避免散弹式修改;
  7. 1. 由于是从客户端在调用工厂的时候,传入选择的参数,**这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理**。这就**要求必须在一定程度上,向客户暴露一定的内部实现细节**。
  8. <a name="FsUwu"></a>
  9. ### 本质
  10. **注意简单工厂的重点在选择,实现是已经做好了的。**就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。**简单工厂的目的在于为客户端来选择相应的实现,从而使得客户端和实现之间解耦**,这样一来,具体实现发生了变化,就不用变动客户端了,这个变化会被简单工厂吸收和屏蔽掉。
  11. <a name="kCLHs"></a>
  12. ### 扩展
  13. 如上节所述,简单工厂的本质是选择,那么如何更好的封闭选择的过程,或者实现动态选择,是简单工厂的关键所在。<br />除了上面的switch实现以外,可以使用查表法的方式实现。
  14. ```java
  15. public class SampleFactory {
  16. private static final Map<String, Class<?>> CLAZZ_MAP = new HashMap<>() {
  17. {
  18. put("C7x", C7xConfigPage.class);
  19. put("MDC", MdcConfigPage.class);
  20. put("Lihis", LihisConfigPage.class);
  21. }
  22. };
  23. public static ConfigPage createConfigPage(String createType) throws NoSuchMethodException,
  24. IllegalAccessException, InvocationTargetException, InstantiationException {
  25. Class<?> createClass = CLAZZ_MAP.get(createType);
  26. if (createClass == null) {
  27. return null;
  28. }
  29. return (ConfigPage)createClass.getDeclaredConstructor().newInstance();
  30. }
  31. }

既然可以通过查表法实现,那么肯定也可以根据配置文件的方式进行实现。因为与具体语言特性有关,因此这里不继续展开。

使用场景

  1. 期望隔离使用方与实现方的
  2. 期望将对象的创建集中起来进行管理和控制

    相关模式

    与工厂模式的区别

    简单工厂和工厂方法模式也是非常类似的。工厂方法的本质也是用来选择实现的,跟简单工厂的区别在于工厂方法是把选择具体实现的功能延迟到子类去实现。如果把工厂方法中选择的实现放到父类直接实现,那就等同于简单工厂。

与抽象工厂模式的区别

抽象工厂模式针对一个产品簇而言的,工厂模式是针对单个产品而言的,只有产品簇只有一个产品时,抽象工程会退化成为工厂模式

与其他创建型模式的区别

工厂模式的重点在于选择,因此可以与其他任意创建型模式相结合使用。

工厂模式

根据简单工程模式中的描述,工厂模式与简单工程模式的唯一差异就是将选择延时到子类中实现。因此这里讲工厂模式与简单工程模式使用相同的业务背景。

常见的code实现

  1. public abstract class ConfigPageFactory {
  2. public abstract ConfigPage createConfigPage();
  3. }
  4. public class C7XConfigPageFactory extends ConfigPageFactory {
  5. @Override
  6. public ConfigPage createConfigPage() {
  7. return new C7xConfigPage();
  8. }
  9. }
  10. public class LihisConfigPageFactory extends ConfigPageFactory {
  11. @Override
  12. public ConfigPage createConfigPage() {
  13. return new LihisConfigPage();
  14. }
  15. }
  16. public class MdcConfigPageFactory extends ConfigPageFactory {
  17. @Override
  18. public ConfigPage createConfigPage() {
  19. return new MdcConfigPage();
  20. }
  21. }

对应的UML图

设计模式—工厂模式 - 图4

  1. 如果直接将对应的ConfigPage接口返回给客户端,在客户端的使用上,并没有任何的区别,都是通过Factory得到对应的接口实现类;
  2. 每个Factory只与一个具体的ConfigPage相关联,符合单一职责原则。
  3. 但其实类增多了,且把工厂搞的很复杂了。

如果仅仅是上述这种UML的结构的话,个人觉得还没有简单工厂来的方便和使用。但是后面看到另外一种解释,觉得这种方式才是工厂模式的典型使用场景。

工厂方法模式的本意,是由工厂对象内部的方法来使用工厂方法创建的对象,也就是说,工厂方法一般不提供给工厂外部使用。那如何理解这句话呢?talk is cheap,show me the code。

  1. public abstract class ConfigPageFactory {
  2. public void repaint() {
  3. ConfigPage configPage = factoryMethod();
  4. configPage.repaint();
  5. }
  6. public abstract ConfigPage factoryMethod();
  7. }
  8. public class Client {
  9. public static void main(String[] args) {
  10. ConfigPageFactory factory = new C7XConfigPageFactory();
  11. factory.repaint();
  12. }
  13. }

这种方式的实现中,客户端不直接跟产品类和产品接口交互,只与工厂类进行交互,并且由工厂类提供相应的方法。只有这样讲具体的实现延后到子类中初始化才有价值。因为子类

这种结构就和模板模式很像,模板模式的子类关注的是为某种算法提供其必要的步骤,工厂模式则关注的是类的创建。

抽象工厂

业务场景介绍

配置页面共有三个,每个配置页面根据产品形态的不同,可能会不同。这些页面共同组成了这个业务所需要的所有页面配置。

编码实现

code

  1. public interface FirstPage {
  2. void repaint();
  3. }
  4. public interface SecondPage {
  5. void repaint();
  6. }
  7. public interface ThirdPage {
  8. void repaint();
  9. }
  10. public class CommonFirstPage implements FirstPage {
  11. @Override
  12. public void repaint() {
  13. System.out.println("Common first page");
  14. }
  15. }
  16. public class C7xFirstPage implements FirstPage {
  17. @Override
  18. public void repaint() {
  19. System.out.println("C7x first page");
  20. }
  21. }
  22. public class C7xSecondPage implements SecondPage {
  23. @Override
  24. public void repaint() {
  25. System.out.println("C7x second page");
  26. }
  27. }
  28. public class C7xThirdPage implements ThirdPage {
  29. @Override
  30. public void repaint() {
  31. System.out.println("C7x third page");
  32. }
  33. }
  34. public class Client {
  35. public static void main(String[] args) {
  36. FirstPage firstPage = new C7xFirstPage();
  37. SecondPage secondPage = new C7xSecondPage();
  38. ThirdPage thirdPage = new C7xThirdPage();
  39. }
  40. }

这段代码最主要的问题是:没有体现出FirstPage、SecondPage、ThirdPage之间的关系,这三者必须都存在,系统才能正常运转。那么三者之间的关系有:

  1. 要都存在;
  2. 相互之间要匹配。C7x系列的就只能使用C7x的所有产品。

对应的UML

设计模式—工厂模式 - 图5

使用模式后的代码

  1. public abstract class AbstractFactory {
  2. public abstract FirstPage createFirstPage();
  3. public abstract SecondPage createSecondPage();
  4. public abstract ThirdPage createThirdPage();
  5. }
  6. public class C7xFactory extends AbstractFactory {
  7. @Override
  8. public FirstPage createFirstPage() {
  9. return new C7xFirstPage();
  10. }
  11. @Override
  12. public SecondPage createSecondPage() {
  13. return new C7xSecondPage();
  14. }
  15. @Override
  16. public ThirdPage createThirdPage() {
  17. return new C7xThirdPage();
  18. }
  19. }
  20. public class Client {
  21. public static void main1() {
  22. AbstractFactory c7xFactory = new C7xFactory();
  23. FirstPage firstPage = c7xFactory.createFirstPage();
  24. SecondPage secondPage = c7xFactory.createSecondPage();
  25. ThirdPage thirdPage = c7xFactory.createThirdPage();
  26. }
  27. }

上述代码解决了:

  1. 确保了页面之间的匹配性;
  2. 不能保证页面之间的完备性;

这里需要注意一点是,这个产品之间,可以进行复用,例如FirstPage只有一种实现,每个不同类型的工程使用同一个类就可以了,也就是产品之间可以任意进行组合。并不是一定的一个工程类型,必须有一个相关联的产品类型。

对应的UML及其优缺点

设计模式—工厂模式 - 图6 优点:

  1. 客户端不知道具体的产品,保证了与产品之间的隔离性;
  2. 产品簇中产品的切换很容易,只需要创建新的工程类就行;

缺点:

  1. 如果产品族中扩展一种新的产品,所有已经存在的类都需要变动;
  2. 类的数量急剧膨胀,维护成本增加;

本质

抽象工厂的本质是为一系列相关对象或相互依赖的对象创建一个接口,一定要注意,这个接口内的方法不是任意堆砌的,而是一系列相关或相互依赖的方法。类似上面例子中的三个页面之间的关系。
针对每一个产品而言,抽象工厂又可以看成是普通的工厂模式,将产品的常见延后到普通工厂中进行实例化

扩展

如果要新增一种类型,常见的方式就是新增一个工程,符合开闭原则。

  1. public class MdcThirdPage implements ThirdPage {
  2. @Override
  3. public void repaint() {
  4. System.out.println("Mdc third page");
  5. }
  6. }
  7. public class MdcFactory extends AbstractFactory {
  8. @Override
  9. public FirstPage createFirstPage() {
  10. return new C7xFirstPage();
  11. }
  12. @Override
  13. public SecondPage createSecondPage() {
  14. return new C7xSecondPage();
  15. }
  16. @Override
  17. public ThirdPage createThirdPage() {
  18. return new MdcThirdPage();
  19. }
  20. }

另外一种扩展,如果需要在产品族中新增一类产品,那么所有已经存在的工厂类都需要进行新增。

使用场景

  1. 当需要创建一簇产品时,且产品之间有一定的关联性时;