如果把创建看作一个职责,那么系统中的哪个对象应该拥有这个职责呢?如果把创建看作知识,那么创建知识应该放置在什么地方呢?说到职责我们不得不说一下著名的GRASP原则:

GRASP是通用职责分配软件模式(General Responsibility Assignment Software patterns)的简称。它包含了9大模式,分别如下所示:

        1  创建者(Creator) :决定对象应该有谁来创建的问题。

        2  信息专家(Information expert):用此模式来确定如何给对象分配职责的问题。一般把职责分配给那些包含此职责有关信息的对象。这样也体现了高内聚性模式。

        3 低耦合(Low coupling)

        4 控制器(Controller).

        5 高内聚(High Cohesion)

        6 多态性(polymorphism)

        7 纯虚构(pure fabrication)

        8 间接性(indirection)

        9 防止变异(protected variations)
     
可以看出GRASP非常关注对象由谁来创建,并给出了一个职责分配的解决模型:信息专家模式;我们不必深究GRASP的种种细节,我们从上面的表述中可以得到这样的启示:

  • 对象的创建不是随意性的,也是有规范可以遵循的,我们可以从中得到灵活性和可维护性;
  • 职责分配给这个职责相关的信息专家,即:最适合的人做最适合的事情

 

       当创建一个对象的知识散布在系统的各处的时候,这种蔓延现象实际上说明了系统创建职责分配的混乱,缺少一个进行拥有创建知识的信息专家(请注意这里的表述)。 创建逻辑通常是这样的,它包含了一系列if-else的判断逻辑,根据运行时的具体参数来决定对象的创建。如果这部分是确定的那么我们也不必过多的考虑它。但是如果它是变化的,比如删减一个对象的创建或者增加一个新类型的创建,那么这部分代码是要被经常修改的,这种修改实际上说明我们已经违反了OCP原则(关闭修改,打开扩展),那么根据识别变化封装变化的原则我们必须把这一部分进行抽取并进行隔离。
      翻看所有设计模式相关的书籍我们都可以看到这样的表述:

      工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以在运行时动态决定将哪一个类实例化。
    
    
看样子工厂模式正是我们需要的,但我们并不急于下手,看看是不是另有蹊径,[接口]怎么样?《Java与模式》一书就是给出了这样一个接口实现的方式。

       我习惯把接口理解成契约和行为,创建出来不同的对象,对象的区别实际上行为的差别。接口是不是可以?假设我么使用了接口,问题解决了么?没有!增加新类时同样面临修改代码的问题。这个方法只是转化了问题的形式,并没有真正解决问题,使用接口我们失去的代码复用的优势,而大量的具体类上使用接口不是什么好主意,虽然《Java与模式》一书全部使用接口实现。如果要创建的对象没有共同的业务逻辑那么可以使用一个接口来扮演抽象产品的角色,但是更多的情况是具体产品之间是存在共有的业务逻辑,那么这些逻辑就应该移到抽象角色里面。
      其实我们就是将创建知识集中并设立一个信息专家专门负责对象的创建。简单工厂模式是我们的第一个拥有创建知识的信息专家:
简单工厂
      我们考虑最简单的抽取和隔离方法就是使用[

简单工厂

]。简单工厂的特点就是参数化创建对象,简单工厂必须知道每一种产品以及何时提供给Client。有人会说简单工厂还是换汤不换药,添加新类的时候还是需要修改这部分的代码!诚然,那么我们获得了什么好处呢?集中变化! 这很好的符合了DRY原则(Don't Repeat Yourself!)创建逻辑存放在单一的位置,即使它变化,我们也只需要修改一处就可以了。DRY 很简单,但却是确保我们代码容易维护和复用的关键。DRY原则同时还提醒我们:对系统职能进行良好的分割!职责清晰的界限一定程度上保证了代码的单一性。这句话对我们后续的分析极具指导意义,毕竟简单工厂只是低层次上的代码复用。
    

      题外话:简单工厂的确简单但是其背后的DRY原则在实践中让我们受益匪浅,去年我和我的搭档做站点的升级工作,写了很多重复的代码;代码重复,源代码组织混乱,没有做好规划和职责分析是原罪。今年新项目,DRY原则是我头顶的达摩克利斯之剑,不做重复的事情成为我进行项目计划组织管理的重要标准。
    关于简单工厂模式的阶段总结:

  • 识别变化隔离变化,简单工厂是一个显而易见的实现方式
  • 简单工厂将创建知识集中在单一位置符合了DRY
  • 客户端无须了解对象的创建过程,某种程度上支持了OCP
  • 添加新的产品会造成创建代码的修改,这说明简单工厂模式对OCP支持不够
  • 简单工厂类集中了所有的实例创建逻辑很容易违反高内聚的责任分配原则。
     

Factory Method模式

    简单工厂模式之所以对OCP支持不够,就是因为它拥有了太多的知识:有多少种产品以及创建产品的时机。或者说,简单工厂承担了太多的职责。DRY原则提示我们要清晰的划清对象职责的界限。一个方法就是权力下放,将创建的知识移交给子类。知识的递交意味着职责的转移。
    这样做之后,我们重新审视类之间的关系:核心工厂类不再负责所有产品的创建,核心类的层次上升成为一个抽象工厂角色,仅仅负责给出具体子类必须实现的接口,而不关系具体产品的创建细节。创建的知识由具体工厂拥有。而这时系统内也出现了一个平行的等级结构,产品家族的等级结构以及对应的工厂等级结构。
    简单工厂把核心放在一个具体类上。Factory Method模式把核心放在抽象类,具体工厂类继承了创建行为。具体工厂都拥有相同的接口所以还有一个别名叫多态工厂模式。这是我们关注点已经从实现转移到了“接口”:关注动机而非实现,是基本的OO设计原则,将实现隐藏在接口之后实际上是将对象的实现与它们的对象解耦了。从“依赖”的角度,系统中依赖的已经不再是一个个具体的实现,而是一个抽象。这就是DIP原则高层模块不应该依赖低层模块,两者都应该依赖于抽象。这个原则隐含的意思是:对象之间只在概念层次存在耦合,在实现层次不能耦合!

     工厂方法模式的应用需要完全遵守里氏替换原则为前提。即父类出现的地方能够替换成子类,工厂方法模式的应用才能成为可能。
      我们要添加一个新的产品,只要添加这个产品类和它的工厂类就可以了。没有必要修改Client和已经存在的工厂代码。Factory Method完全支持OCP原则

抽象工厂模式


    抽象工厂向客户端提供了一个接口,使得客户端在不指定具体产品类型的时候就可以创建产品族中的产品对象。这就是抽象工厂的用意。抽象工厂面的问题是多个等级产品等级结构的系统设计。抽象工厂和工厂方法模式最大的区别就在于后者只是针对一个产品等级结构;而抽象工厂则是面对多个等级结构。

      同样出色的完成了把应用程序从特定的实现中解耦,工厂方法使用的方法是继承,而抽象工厂使用的对象组合。抽象工厂提供的是一个产品家族的抽象类型,这个类型的子类完成了产品的创建。
       我们在工厂方法模式中提到的OCP DIP LSP等原则的表述也适用于抽象工厂模式.
       我曾经在《视角的力量--再说OO设计原则》一文中提到抽象出来高层策略是需要有一定稳定性的。抽象工厂作为一个高层抽象如果它的接口发生变化,那么影响是巨大的:所有的子类都要进行修改!这就要求抽象工厂的接口设计是高度抽象的!
 

总结:

  • 发现变化隔离封装变化是动因,关闭修改打开扩展是限制
  • 简单工厂很好地遵守了DRY原则,对OCP原则支持不足
  • 工厂方法模式完全支持了OCP原则,使用的机制是继承
  • 抽象工厂模式 工厂方法模式都完全支持OCP原则
  • LSP原则是OCP成为可能的重要原则,抽象工厂模式、工厂方法模式完全遵守LSP
  • 依赖于抽象,一个类派生自具体类明显违背了DIP原则
  • GRASP是另外一个设计模式体系,它与GOF设计模式在很多地方是殊途同归,了解一下GRASP可以帮助我们思考
     

参考阅读:OO设计原则总结   视角的力量--再说OO设计原则