Design Pattern学习笔记之工厂(简单工厂、工厂方法、抽象工厂)

来源:互联网 发布:琅琊榜国外知乎 编辑:程序博客网 时间:2024/06/05 04:21

Design Pattern学习笔记之工厂(简单工厂、工厂方法、抽象工厂)

1. 有点邪恶的new

    我们都知道应该面向接口编程,可new方法让代码明确无比地跟具体的类绑定在一起,这样后续的变化就会对原有代码造成冲击,违背了设计中一直提倡的原则: design should be "open for extension but closed for modification"。我们来看看示例代码:
    Duck duck;

    if(picnic){

        duck = newMallardDuck();

    }

    else if(hunting){

        duck = newDecoyDuck();

    }

    else if(inBathTub){

        duck = newRubberDuck();

    }

    怎么应对变化?可回到设计的第一个原则:找到变化的地方,封装它们

2. 伟大的披萨店--问题引入

    设想我们要开个披萨店,会提供各种口味的披萨饼,每个披萨的制作都要通过准备材料、烘焙、分割和打包四道工序。那我们先抽象出披萨,做成接口,在该接口中定义披萨制作过程中要做的工作;再有个披萨商店的类,有个处理披萨订单的方法,那这个方法就是如下的样子:

    public class PizzaStore{

       public Pizza orderPizza(String type){

              Pizza pizza = null;

              if("cheese".equals(type)){

                     pizza = new CheesePizza();

              }

              else if("greek".equals(type)){

                     pizza = new GreekPizza();

              }

              else if("clam".equals(type)){

                     pizza =  new ClamPizza();

              }

              if(pizza == null){

                     return null;

              }

              pizza.prepare();

              pizza.bake();

              pizza.cut();

              pizza.box();

              return pizza;

       }

    }

orderPizza方法要求传入参数来决定顾客需要的是什么口味的披萨。

    第一步改造:识别出变化的内容,封装这些内容

    public classSimplePizzaFactory {

       public Pizza createPizza(String type){

              Pizza pizza = null;

              if("cheese".equals(type)){

                     pizza = new CheesePizza();

              }

              else if("greek".equals(type)){

                     pizza = new GreekPizza();

              }

              else if("clam".equals(type)){

                     pizza =  new ClamPizza();

              }

              return pizza;

       }

    }

    public classPizzaStore {

       SimplePizzaFactory factory;

       public PizzaStore(SimplePizzaFactory factory){

              this.factory = factory;

       }     

       public Pizza orderPizza(String type){

              Pizza pizza;       

              pizza = factory.createPizza(type);          

              pizza.prepare();

              pizza.bake();

              pizza.cut();

              pizza.box();

              return pizza;            

       }

    }

    好处在哪里?因为看起来只是将原来在PizzaStore 的麻烦给转移到SimplePizzaFactory而已。

    a. 创建不同pizza的任务被独立出来,有利于该方法的复用

    b. PizzaStore实现了面向接口编程,跟具体的pizza类实现了松散耦合

3. 宣教篇--简单工厂

    简单工厂不是正式的设计模式之一,因为它太简单,超轻量级,更像我们讨论设计或者实现的一种习惯用语,但是由于简单易用,在实际中简单工厂也获得了广泛的应用。以下是应用简单工厂披萨店的类图:

4. 伟大的披萨店--披萨连锁店

    你的披萨店生意越来越好,现在全国各地都有人要求加盟你的披萨店,你希望把披萨店做成连锁店,这样的话,就有新的问题需要面对:1. 各个地方的人们对披萨要求的口味不同,同一种披萨因为地域不同,制作方法可能不一样;2.出于对质量掌控的考虑,把制作披萨的方式委托给另外一个类(简单工厂)不是个好方法,你希望在披萨店中定义不同口味披萨的制作。

    归根结底,我们希望把制作披萨的责任从简单工厂类中移回披萨店中,让披萨店负责披萨的制作,同时还要保持一定的灵活性,增加其他地区的连锁店时,不会对已有的连锁店造成影响。

    一种方法是把披萨店做成抽象类,保持运行良好的处理披萨订单的方法不变,增加抽象的制作披萨方法,由不同地域的具体披萨店定义披萨的制作;orderPizza方法不管该披萨是按照哪个地域的方法制作的,只知道它是个披萨(是抽象产品pizza的子类),就可完成订单处理的工作,从而实现订单处理方法和披萨制作的松散耦合,连锁店中增加新地域的披萨店时,只要创建该地域的披萨商店工厂即可。代码如下:

    public abstract classPizzaStore {

       public Pizza orderPizza(String type){

              Pizza pizza;       

              pizza = createPizza(type);         

              pizza.prepare();

              pizza.bake();

              pizza.cut();

              pizza.box();

              return pizza;

       }     

       abstract Pizza createPizza(String type);

    }

    public classNYPizzaStore extends PizzaStore{

       Pizza createPizza(String type) {

              if("cheese".equals(type)){

                     return new NYStyleCheesePizza();

              }

              else if("clam".equals(type)){

                     return new NYStyleClamPizza();

              }

              return null;

       }

    }

    再来看看类图:

    

    工厂方法的定义:

    A Factory Method handlesobject creation and encapsulates it in a subclass. This decouples the clientcode in the super class from the object creation code in the subclass.

    工厂方法在子类中封装对象的创建工作,实现父类中客户端代码跟子类中对象创建代码之间的松散耦合。(父类中的代码依赖于子类创建的对象完成工作)

5 宣教篇--工厂方法

    The Factory Method Patterndefines an interface for creating an object, but lets subclasses decide whichclass to instantiate. Factory Method lets a class defer instantiation tosubclasses.

    工厂方法定义一个创建对象的接口,但是将如何实例化该对象委托给子类,由子类决定具体应该创建哪个对象。

    类图:

    

    定义中的“决定”是什么意思?

    一组问题:

    Q 如果只有一个具体的创建者,那么应用该模式有什么意义?

    A 实现对象创建和对象使用的松散耦合。

    Q 对于某一个具体的创建者,它的实现更像一个简单工厂?

    A 最大的不同是简单工厂由另外一个类负责产品的创建而工厂方法由自身负责产品的创建。

    Q 简单工厂和工厂方法的区别?

    A 简单工厂是一锤子买卖,工厂方法提供了一个如何创建对象的框架;简单工厂和工厂方法都提供了对创建对象的封装,但是简单工厂不够灵活,它没有能力区分不同的产品。

6. 宣教篇--依赖反转原则(the dependency inversion principle)

    Depend upon abstractons. Donot depend upon concrete classes. 依赖于抽象类而不是具体类。

    重新温习下最初的披萨店实现:

     public classPizzaStore {

       public Pizza orderPizza(String type){

              Pizza pizza = null;

              if("cheese".equals(type)){

                     pizza = new CheesePizza();

              }

              else if("greek".equals(type)){

                     pizza = new GreekPizza();

              }

              else if("clam".equals(type)){

                     pizza =  new ClamPizza();

              }

              if(pizza == null){

                     return null;

              }

              pizza.prepare();

              pizza.bake();

              pizza.cut();

              pizza.box();

              return pizza;

       }

    }

    看一下依赖关系:

    

    pizzaStore要依赖于各种各样不同口味的披萨,增加新口味的披萨时需要修改pizzaStore的代码

    应用简单工厂后的依赖关系:

    

    pizzaStore和各种不同的披萨都依赖于抽象的pizza(接口或者抽象类),增加新口味的披萨时,对于pizzaStore没有影响。

    为什么叫依赖反转?

    其实更多描述的是一种思考问题的方式,而不是具体设计或者实现。从披萨店这个例子,我们再来一次思考的过程,看一下反转的过程。

    a. 要你设计个披萨店,首先你会怎么想?pizzaStore应该能处理披萨订单,披萨需要烘焙、分割、打包...,需要能制作芝士口味、黑胡椒口味,等等各种口味的披萨。

    b. 如果从如何创建披萨店的思路一直向下,就会到各种具体的披萨上。现在我们换个角度考虑问题,从披萨着手,有a披萨、b披萨等等,它们都需要烘焙、分割、打包,它们需要不同的制作过程,归根结底它们都是披萨。那我们做统一的抽象,把公共的东西做成接口,把个性的东西封装在创建不同披萨的方法中。至此完成pizzaStore和pizza的松散耦合。

    避免破坏依赖反转准则的几个建议:

    a. 类中的每一个成员变量都不能持有一个具体类的引用(应该是接口或者抽象类)

    b. 每一个类都不应该继承自一个具体的类(应该继承自抽象类)

    c. 类中的每一个成员方法都不能覆盖父类已经实现的方法

    需要说明的是,以上建议在实际设计和实现过程中总是不可能完全达到,但是在这里强调的目的是在不能遵从以上建议时,一定要有充分的理由,这样才能保证你的设计足够灵活。

7.伟大的披萨店—新问题

    目前你的披萨连锁店运营的不错,新加盟的连锁店都能按照设定好的流程满足订单要求,提供适合本地消费者口味的披萨。但是随着规模的扩大,现在有了新的问题:开始有不法的连锁店采用劣质的原料来制作披萨,以降低成本获取超额利润。如何解决这个问题?建立原材料工厂是个不错的选择,统一生产原材料然后配送到各个连锁店,就可解决这个问题。

    由于各地消费者口味不同,不同连锁店制作的相同品种的披萨可能需要不同的原料,如何把各个连锁店需要的原料准确运送到连锁店,并且在新增加连锁店、新增加不同品种的原料时,不影响已有的连锁店,这还真是个挑战。

    我们来看看原料的差异:

    芝加哥的披萨连锁店需要以下原料:FrozenClams,PlumTomatoSauce, ThickCrustDough, MozzarellaCheese

    纽约的披萨连锁店需要以下原料:FreshClams,MarinaraSauce, ThinCrustDough, ReggianoCheese

    如何应对该问题?

    我们计划设计原材料的工厂来满足要求,先定义原材料工厂的接口,以确定每个具体的原材料工厂应该提供的原材料;再创建不同地域的原材料工厂以满足各地对原材料的不同需求。

    代码如下:

    生产原料的工厂接口:

    public interfacePizzaIngredientFactory {

       public Dough createDough();

       public Sauce createSauce();

       public Cheese createCheese();

       public Clam createClam();

    }

    一个具体的原料工厂:

    public classNYPizzaIngredientFactory implements PizzaIngredientFactory{

       public Cheese createCheese() {

              return null;

       }

        publicClam createClam() {

              return null;

       }

       public Dough createDough() {

              return null;

       }

       public Sauce createSauce() {

              return null;

       }

    }

    抽象的披萨类:

    public abstract classPizza {

       Dough dough;

       Sauce sauce;

       Cheese cheese;

       Clam clam;

       //准备制作披萨的各种原材料

       abstract void prepare();

      

       public void bake(){

              System.out.println("bakePizza");

       }

       public void cut(){

              System.out.println("cut Pizza");

       }

       public void box(){

              System.out.println("box Pizza");

       }

    }

    芝士风味的披萨:

    public classCheesePizza extends Pizza{

       PizzaIngredientFactory ingredientFactory;

      

       public CheesePizza(PizzaIngredientFactoryingredientFactory){

              this.ingredientFactory =ingredientFactory;

       }

 

       @Override

       void prepare() {

              dough = ingredientFactory.createDough();

              sauce = ingredientFactory.createSauce();

              clam = ingredientFactory.createClam();

              cheese = ingredientFactory.createCheese();           

       }

    }

    统一的披萨商店:

    public abstract classPizzaStore {

       public Pizza orderPizza(String type){

              Pizza pizza;

             

              pizza = createPizza(type);

             

              pizza.prepare();

              pizza.bake();

              pizza.cut();

              pizza.box();

              return pizza;

       }

      

       public abstract Pizza createPizza(String type);

    }

    纽约地区的披萨商店:

    public classNYPizzaStore extends PizzaStore{

 

       @Override

       public Pizza createPizza(String type) {

              Pizza pizza = null;

              PizzaIngredientFactory ingredientFactory =new NYPizzaIngredientFactory();

              if("cheese".equals(type)){

                     pizza = newCheesePizza(ingredientFactory);

              }

              else if("clam".equals(type)){

                     pizza = new ClamPizza(ingredientFactory);

              }

              return pizza;

       }

    }

    现在的披萨商店跟原来相比,最大的区别是引入原料工厂,由该工厂来创建制作披萨的一系列原料;披萨只知道抽象的原料工厂,不了解具体原料的创建,实现原料创建和披萨的松散耦合;不同地域原料的创建由不同地域的原料工厂具体负责,增加新地域原料的生产,只需创建具体地域的原料工厂,对原有原料工厂和pizza不造成影响。

8. 宣教篇--抽象工厂模式

    The Abstract Factory Patternprovides an interface for creating families of related or dependent objectswithout specifying their concrete classes.

    抽象工厂模式提供一个创建一组相互依赖(相互关联)产品的接口,客户端使用该接口获取、使用产品,而不需了解具体产品,实现客户端和具体产品的松散耦合。

    抽象工厂模式中创建某一个产品时应用工厂方法模式。

    看看类图:

    

9. 其他

工厂方法模式和抽象工厂模式的异同

    a. 都能实现产品使用者跟具体产品的松散耦合

    b. 工厂方法模式和抽象工厂模式都实现对创建产品的封装

    c. 工厂方法模式通过继承,由子类覆盖父类的工厂方法实现产品创建;抽象工厂模式通过对象组合技术,在产品使用者对象中引入工厂对象实现产品创建

    d. 工厂方法模式创建单一产品;抽象工厂模式将一组相关联的产品组合到一起

    e. 抽象工厂模式在要新增产品时,需要修改接口,同时需要修改其他已经实现该接口的具体工厂

    OO准则

    a. 封装变化, encapsulate whatvaries

    b. 组合优于继承, favor compositionover inheritance

    c. 面向接口编程, program to interfaces,not implementation

    d. 致力于实现交互对象之间的松散耦合, strivefor loosely coupled designs between objects that interact

    e. 类应该对于扩展开发,对于修改封闭, classesshould be open for extension but closed for modification

    f. 依赖于抽象类或接口, depend onabstraction. do not depend on concrete classes.

    关于工厂模式的总结

    a. 简单工厂、工厂方法、抽象工厂都实现对对象创建的封装

    b. 简单工厂是最简单的一种方式,可实现client跟具体类的松散耦合

    c. 工厂方法依赖于继承,父类把创建对象的任务委托给覆盖工厂方法的子类

    d. 抽象工厂依赖于组合,对象创建由实现工厂接口的具体工厂类实现

    e. 所有工厂都通过降低client对具体类的依赖来实现松散耦合

    f. 工厂方法的目标是把对象创建的任务委托给子类

    g. 抽象工厂的目标是不依赖于具体类,创建一组相关联对象

    h. 依赖反转原则指引我们尽量不要依赖于具体类,应该依赖于抽象类或者接口

 

原创粉丝点击