美文网首页
设计模式之工厂模式(为什么很多人觉得工厂模式没有用)

设计模式之工厂模式(为什么很多人觉得工厂模式没有用)

作者: yzbyzz | 来源:发表于2019-06-29 08:28 被阅读0次

    前言

    随着编程技术的不断发展,面向对象语言和面向对象程序设计逐渐成为主流。这就不可避免地涉及到了对象的创建。创建一个对象,并使用已经定义好的方法,看起来也很清晰和简单。有的时候,在不同的情况下需要不同子类的对象,如何降低耦合度、方便地进行切换,而不需要将所有实例化该对象的地方都进行修改,则涉及到了模式。

    下面将依次介绍简单工厂模式、工厂方法模式、抽象工厂模式,说明他们是如何实现创建对象这一功能的。(后文将三者统称为工厂模式。)

    简单工厂模式

    一个很简单的做法,既然你说可能要使用不同的对象,那我将所有创建对象的操作都集中起来,只在一个地方进行对象的创建,其他地方都通过这里来创建对象。以后即便要修改,也只需要改一处地方不就可以了。

    以生产鼠标为例,我们需要有线鼠标和无线鼠标。则大致的代码如下:

    abstract class Mouse {
    }
    
    class WiredMouse : Mouse {
    }
    
    class WirelessMouse : Mouse {
    }
    
    class SimpleFactory_1 {
        public Mouse createMouse(String type) {
        if ("wired".equals(type)) {
            return new WiredMouse();  
        } else if ("wireless".equals(type)) {
          return new WirelessMouse();
        } else {
          // unknown type !
          return null;
        }
      }
    }
    

    简单工厂模式十分简单直观,代码量也很少。这份代码的一个改进之处是将 createMouse 的参数从 String 类型改成枚举类型,以免使用时拼写错误,也能更好地对参数进行限制。

    简单工厂模式的缺点是,如果我们需要增加新的鼠标,那么我们就必须修改 createMouse 方法,增加 if/else 语句。这样就破坏了"对修改封闭"的原则。

    简单工厂模式可以定义成静态方法,也可以定义成实例方法。他们的特点如下:

    • 静态方法
      • 无需创建对象就能使用
      • 不能使用继承来改变创建方法的行为
    • 实例方法
      • 需要创建对象才能使用
      • 可以使用继承来改变创建方法的行为

    更进一步,假设有 A、B 两家公司都生产有线和无线鼠标,他们的鼠标定义如下:

    // A 的鼠标
    class AWiredMouse : WiredMouse {
    }
    
    class AWirelessMouse : WirelessMouse {
    }
    
    // B 的鼠标
    class BWiredMouse : WiredMouse {
    }
    
    class BWirelessMouse : WirelessMouse {
    }
    

    对应的简单工厂模式代码如下:

    class SimpleFactory_2 {
        public Mouse createMouse(String factory, String type) {
        if ("A".equeals(factory)) {
          if ("wired".equals(type)) {
            return new AWiredMouse();  
          } else if ("wireless".equals(type)) {
            return new AWirelessMouse();
          } else {
            // unknown type !
            return null;
          }  
        } else if ("B".equals(factory)) {
          if ("wired".equals(type)) {
            return new BWiredMouse();  
          } else if ("wireless".equals(type)) {
            return new BWirelessMouse();
          } else {
            // unknown type !
            return null;
          }
        } else {
          // unknown factory !
          return null;
        }
      }
    }
    

    可以看到代码开始复杂起来了,如果还有更多公司 C、D 也在生产鼠标,或者有更多的鼠标类型,那么这个工厂类将会更将复杂。

    工厂方法模式

    下面我们就来看看工厂方法模式是如何处理这种问题。

    abstract class Factory {
        public Mouse CreateMouse(String type);
    }
    
    class AFactory_2 : Factory {
      public Mouse CreateMouse(String type) {
        if ("wired".equals(type)) {
          return new AWiredMouse();  
        } else if ("wireless".equals(type)) {
          return new AWirelessMouse();
        } else {
          // unknown type !
          return null;
        } 
      }
    }
    
    class BFactory_2 : Factory {
      public Mouse CreateMouse(String type) {
        if ("wired".equals(type)) {
          return new BWiredMouse();  
        } else if ("wireless".equals(type)) {
          return new BWirelessMouse();
        } else {
          // unknown type !
          return null;
        } 
      }
    }
    

    可以看到,工厂方法模式中,定义了多个工厂类分别对应各自的公司,每个类只负责创建自己的鼠标,而不用管其他公司的。

    如果还有更多的公司,则定义新的工厂类即可,不需要修改已有的代码。

    如果需要生产更多类型的鼠标,则需要对工厂都做修改,增加相应的 if/else 语句来处理。这一点与简单工厂模式类似。

    工厂方法模式,体现了"对扩展开发,对修改封闭"。

    这里为了说明使用工厂方法模式相比简单工厂模式的优点,特意给鼠标增加了公司(A、B)和类型(有线、无线)这两个纬度,以便说明工厂方法模式对代码职责进行划分。

    实际上,对应简单工厂 SimpleFactory_1 也可以改成工厂方法模式,代码如下:

    abstract class Factory {
        public Mouse CreateMouse();
    }
    
    class WiredFactory_1 : Factory {
      public Mouse CreateMouse() {
        return new AWiredMouse();  
      }
    }
    
    class WirelessFactory_1 : Factory {
      public Mouse CreateMouse() {
         return new AWirelessMouse();
      }
    }
    

    抽象工厂模式

    这些公司除了生产鼠标外,还会生产键盘等产品。键盘的定义如下:

    abstract class Keyboard {
    }
    
    class WiredKeyboard : Keyboard {
    }
    
    class WirelessKeyboard : Keyboard {
    }
    
    // A
    class AWiredKeyboard : WiredKeyboard {
    }
    
    class AWirelessKeyboard : WirelessKeyboard {
    }
    
    // B
    class BWiredKeyboard : WiredKeyboard {
    }
    
    class BWirelessKeyboard : WirelessKeyboard {
    }
    

    对应的抽象工厂模式如下:

    abstract class Factory {
      public Mouse createMouse(String type);
      
      public Keyboard createKeyboard(String type);
    }
    
    class AFactory_2 : Factory {
      public Mouse createMouse(String type) {
        // 与之前一致
      }
      
      public Keyboard createKeyboard(String type) {
        if ("wired".equals(type)) {
          return new AWiredKeyboard();  
        } else if ("wireless".equals(type)) {
          return new AWirelessKeyboard();
        } else {
          // unknown type !
          return null;
        } 
      }
    }
    
    class BFactory_2 : Factory {
      public Mouse createMouse(String type) {
        // 与之前一致
      }
      
      public Keyboard createKeyboard(String type) {
        if ("wired".equals(type)) {
          return new BWiredKeyboard();  
        } else if ("wireless".equals(type)) {
          return new BWirelessKeyboard();
        } else {
          // unknown type !
          return null;
        } 
      }
    }
    

    可以看到:现在一个工厂会负责生产鼠标、键盘等多种产品,并且每个工厂只负责自己的部分。

    如果还有新的公司也可以生成鼠标、键盘这些产品,则只需要添加新的工厂类即可,无需修改已有的类。

    如果还要生产更多的产品比如显示器等,则需要修改所有的工厂类。

    这里的例子,工厂方法也可以拆成抽象工厂来表示。。

    总结

    简单工厂模式的重点在于将创建产品的代码统一在一处,方便管理。

    工厂方法模式、抽象工厂模式除了将创建产品的代码统一在一处,还提供了一种便捷地更换产品系列的能力——当我需要 A 公司的产品,那么就使用 AAFactory;当我需要 B 公司的产品,那么就使用 BFactory;只需要修改使用的工厂即可实现将所有的产品都改成对应的产品,大大降低了耦合度和修改的成本。

    很多时候,我们只需要用到简单工厂模式就足够了。但这并不是工厂方法模式、抽象工厂模式没有用,而是很多时候没有这种需求——我们只需要一种类型的产品就足够了,并不需要同时支持多种产品;在这种情况下,使用简单工厂模式的确会更加简单。

    然而,一旦我们同时需要不同类型的产品,那么简单工厂模式就很容易使得方法体快速膨胀,使用工厂方法模式和抽象工厂模式可以更好地对代码和职责进行划分。

    工厂方法模式只生产一种产品,而抽象工厂模式则是生产一系列产品(,通常这些产品之间有一定联系的)。抽象工厂模式生产其中的每个产品时,通常使用工厂方法模式。

    参考

    • 《Head First 设计模式》

    • 《设计模式 可复用面向对象软件的基础》

    相关文章

      网友评论

          本文标题:设计模式之工厂模式(为什么很多人觉得工厂模式没有用)

          本文链接:https://www.haomeiwen.com/subject/eosucctx.html