美文网首页
工厂三兄弟

工厂三兄弟

作者: 小丸子啦啦啦呀 | 来源:发表于2022-02-22 16:58 被阅读0次

    此篇文章仅作为一篇读后感,方便个人记忆,如果需阅读更加详细以及权威的内容,移步史上最全设计模式导学目录

    工厂,顾名思义是一个生产产品的地方,在设计模式中,他是一个生产其他类的实例的类。

    工厂有三兄弟,分别是:

    1. 简单工厂模式
    2. 工厂模式(又名:虚拟构造器模式)
    3. 抽象工厂模式

    他们在复杂度上层层递进,在对复杂场景的支持能力上也是层层递进的,下面将借助实例来描述每一个兄弟的定义,优缺点,和使用场景。

    简单工厂模式

    我在策略+简单工厂模式实践
    这篇文章中使用了简单工厂模式,根据载体类型的不同,生产对应的转换器。

    class ConvertorFactory{
       static getConvertor(type){
          if(type === "excel"){
             return new ExcelConvertor()
          }
          if(type === "vfs"){
             return new VfsConvertor()
          }
       }
    }
    
    const excelConvertor = ConvertorFactory.getConvertor('excel');
    

    简单工厂模式确实是很简单,但是在这个案例里也足够用了。
    简单工厂模式的优点主要有两个:

    1. 分离了创建实例和使用实例这两个职责。在没有工厂之前,既需要创建载体对应的转换器实例,还需要调用转化器上的转换方法,而有了工厂之后,所有创建的工作都交给工厂,职责更加清晰;
    2. 调用者只需要关心它需要什么类型的产品,具体如何生产这些产品不需要其考虑。在我的Case中,只需要告诉工厂type到底是Excel还是VFS即可,具体如何创建是不需要感知的。

    不难看出,这两个有点在我的Case中都发挥了出来,看起来十分完美,但是简单工厂并不能完全覆盖所有的场景。

    不妨设想:

    1. 我们在转换vfs数据时,需要调用者传入用户名和密码是否能正确地访问到vfs数据中指定的路径,如果不能访问,则创建转换器失败;
    2. 我们在转换excel数据时,需要调用者传入支持Excel版本号来确定对否能转换, 如果不能访问,则创建转换器失败。

    这时,我们使用简单工厂方法就难办到了,因为作为调用者,不仅需要知道载体类型,还要提供对应的参数才能拿到实例。

    工厂模式

    而工厂模式正是为了解决这个问题而出现,直接来看看如果采用工厂模式如何做:

    // 抽象产品(转换器)
    // 实际中我用的是interface, 为了更加还原刘老师的例子这里用抽象类
    abstract Convertor{ 
       abstract digitalize(){}
       abstract visualize(){}
    }
    // 具体产品(Excel转换器)
    class ExcelConvertor extends Convertor{
      digitalize(){
         ... 
      }
      visualize(){
        ...
      }
    }
    // 具体产品(VFS转换器)
    class VfsConvertor extends Convertor{
      digitalize(){
         ... 
      }
      visualize(){
        ...
      }
    }
    // 抽象工厂
    abstract ConvertorFactory{
       createConvertor(){}
    }
    // 具体工厂
    class ExcelConvertorFactory extends ConvertorFactory{
      createConvertor(versions){
          if(valid(versions)){
            return new ExcelConvertor();
          }else{
            throw Error("Cannot acess excel")
          }
      }
    }
    
    class VfsConvertorFactory extends ConvertorFactory{
      createConvertor(username, credential){
          if(valid(username, credentials)){
            return new VfsConvertor();
          }else{
            throw Error("Cannot acess fold")
          }
      }
    }
    // client
    factory: ConvertorFactory = new ExcelConvertorFactory();
    factory.createConvertor(['v1', 'v2'])
    

    从以上案例可以看出,具体工厂和具体产品是一对一出现的,每个工厂只负责生产一种产品,但是每个工厂都继承自抽象工厂,所以他们都实现了同一个方法: createConvertor.

    工厂模式的优点主要有两点:

    1. 调用者完全面向工厂,他不需要像简单工厂模式中那样,在一个中心化的管家那里传入类型来获取实例,现在如果需要某种产品,就去找那个产品对应的工厂来生产;
    2. 由于多态性(也就是声明为父类类型创建的是子类的实例),当需要新增产品时,只需要新增一个具体产品和具体工厂即可,不需要改动其他代码。

    同简单工厂方法一样,工厂方法也不能cover掉所有的场景。上文说到,每个具体工厂仅负责生产一种具体产品,那么当有多个产品他们具有一些共性时,如果依旧为他们每种产品都创建工厂,那么工厂类的数量将急剧上升。

    这里,我不再基于已有CASE编例子了,而用刘老师在2 产品等级结构与产品族提到的电视机、电冰箱、空调的例子,加以不切实际的想象来作为案例。

    假设需要采购一批家电,包括电视机、电冰箱两种;品牌可从美的和海尔里面选择,但是为了拿到更低的采购价,要么全部都从海尔买,要么全部都从美的买。要求可以随时切换品牌算出总价。

    如果我们不做任何改进,依旧使用工厂模式:

    abstract class Haier(){
       getBrand(){
           return “I am Haier”
       }
       abstract getPrice():number
    }
    class HaierTV extends Haier(){
      getPrice(){
         return 1000;
      }
    }
    class HaierFrige extends Haier(){
     getPrice(){
         return 1500;
      }
    }
    abstract class Midea(){
       getBrand(){
           return “I am Midea”
       }
      getPrice(): number
    }
    class MideaTV extends Midea(){
      getPrice(){
         return 1000;
      }
    }
    class MideaFrige extends Midea(){
     getPrice(){
         return 1500;
      }
    }
    abstract class Factory{
       abstract create(){}
    }
    // 由于有4种产品,所以需要4个工厂
    class class MideaFrigeFactory extends Factory{
      create(){
        return new MideaFrige()
      }
    }
    // MideaTVFactory, HaierTVFactory, HaierFrigeFactory就省略了,要不然太长了
    
    // Client
    function calculator(){
        const fridgeFactory: Factory = new MideaFrigeFactory();
        const tvFactory: Factory = new MideaTVFactory();
    
        return fridgeFactory.create().getPrice()*10 
                     + tvFactory.create().getPrice*6
    }
    

    代码很长很痛苦,并且当我想增加一个新品牌格力的时候,我需要新建:GreeTVFactory, GreeFrigeFactory, GreeTV, GreeFrige四个新的类。

    然而,不管是什么品牌,始终都是那两样电器,其实不需要那么多工厂,所以能不能让一个一个品牌的工厂既生产电视机,又生产冰箱,还能生产空调呢?当然能!

    抽象工厂模式

    先来直接看看如果用抽象工厂模式如何来做

    abstract class Haier(){
       getBrand(){
           return “I am Haier”
       }
       abstract getPrice():number
    }
    class HaierTV extends Haier(){
      getPrice(){
         return 1000;
      }
    }
    class HaierFrige extends Haier(){
     getPrice(){
         return 1500;
      }
    }
    abstract class Midea(){
       getBrand(){
           return “I am Midea”
       }
      getPrice(): number
    }
    class MideaTV extends Midea(){
      getPrice(){
         return 1000;
      }
    }
    class MideaFrige extends Midea(){
     getPrice(){
         return 1500;
      }
    }
    // 以上抽象产品和具体产品都没变
    
    // 现在建一个抽象工厂,该工厂既可以生产电视机,也可以生产冰箱
    abstract class Factory{
       abstract createTV();
       abstract createFrige();
    }
    
    // 然后开始建海尔工厂和美的工厂
    class HaierFactory extends Factory{
      createTV(){
         return new HairTV()
      }
      createFrige(){
         return new HairFrige()
      }
    }
    
    class MideaFactory extends Factory{
      createTV(){
         return new MideaTV()
      }
      createFrige(){
         return new MideaFrige()
      }
    }
    
    // Client
    function calculator(){
        // 如果更换厂家只需要将MideaFactory替换掉即可
        const factory: Factory = new MideaFactory();
        return factory.createTV().getPrice()*10 
                     + factory.createFrige().getPrice*6
    }
    

    这里,冰箱和电视机构成了一个产品族,同一个品牌的电视机构成了一个产品级;我们需要把产品族(电视机,冰箱)放到一个工厂(海尔,美的工厂)里生产,让工厂生产产品族里的每一种产品。

    分析到这里,可以得出,抽象工厂模式的优点主要有以下两点:

    1. 当出现产品族时,不需要建立那么多工厂了,让一个工厂生产整个产品族的产品,大大减少工厂的数量;
    2. 当需要新增一个产品族时,只需要新建相关的具体工厂,其余的代码都不用修改。

    你可能早就有了疑问,如果说有一天我不想增加一个产品族(品牌)而是想增加一个产品级(空调),那我岂不是要修改所有的工厂(每个品牌都要建立空调厂)?
    确实没错,那是因为开闭原则具有倾向性,在我们确定采用这种模式之前,就要考虑好要采购的所有家电类型,确定我们一般都只切换品牌,而不是更改家电种类。

    这里就引出了抽象工厂模式的缺点:
    增加产品族简单,但是增加产品级困难。

    所以,只有在以下条件满足时才适用抽象工厂模式:

    1. 出现了产品级和产品族,且不能同时使用多个产品族。比如皮肤,我们不能在同一时间既用春季皮肤,又用夏季皮肤;比如采购品牌家电,我们不能既采购美的的又采购海尔的。
    2. 一般只切换产品族,不会轻易增加或者减少产品族里的产品。比如我们在做界面皮肤的时候,就考虑好了所有的界面元素,一般都只会切换皮肤,而不是随意增加或者减少界面元素。

    总结

    以上就是关于工厂三兄弟的全部内容了,总的来说三者各有优劣,主要看所遇到的场景是具有什么特点,能和哪个兄弟匹配上,就让哪个兄弟上。
    目前我的工作中遇到的场景都比较简单,所以使用简单工厂模式就足够了,等将来遇到更加复杂的场景,我再来补充。

    相关文章

      网友评论

          本文标题:工厂三兄弟

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