• 工厂模式factory pattern


     烘烤oo的精华
     我们已经学了3个章节了,还没回答关于new的问题,我们不应该针对实现编程,但是当我们每次使用new时,不正是在针对实现编程吗?
     当看到”new“时,就会想到”具体“
     是的,当使用new时,你的确是在实例化一个具体类,所以用的确实是实现,而不是接口。这是一个好问题,你已经知道了代码绑着具体类会使代码更脆弱。更缺乏弹性。
     Duck duck=new MallardDuck();
    要使用接口让代码具有弹性         ,new 具体类 但是还是得建立具体类的实例。
     
    当有一群相关的具体类时,通常会写出这样的代码:
    Duck duck;
    if(picnic){
         duck=new MallardDuck();
    }else if(hunting){
         duck=new DecoyDuck();
    }else if(inBathTub){
         duck=new RubberDuck();
    }
    有一大推不同的鸭子类,但是必须等到运行时,才知道实例化哪一个。
     
    当看到这样的代码,一旦有变化或扩展,就必须重新打开这段代码进行检查和修改,通常这样的修改过的代码将造成部分系统更难维护和更新,而且也更容易犯错。
     
     但是,总是要创建对象吧!而java只提供了一个new关键字创建对象,不是吗?
     new有什么不对劲?
     在技术上,使用new并没有错,毕竟这是java的基础部分,真正的犯人是我们的老朋友”改变“,以及它是如何影响new的使用的。
      针对接口编程,可以隔离掉以后系统可能发生的一大堆改变,为什么呢?如果代码是针对接口编程,那么通过多态,他可以与任何新类实现该接口,但是,当代码中使用大量的具体类时,等于是自找麻烦,因为一旦加入新的具体类,就必须修改代码。
     
    也就是说,你的代码并非”对修改关闭“,想用新的具体类型来扩展代码,就必须打开它。
     所以,当遇到这样的问题时,就应该回到oo设计原则去寻找线索,别忘了,我们的第一个原则用来处理改变,并帮助我们”找出会变化的方面,把他们从不变的部分分离出来".
     
     
    认识变化的方面
     假设你有一个pizza店,
     Pizza orderPizza(){
          Pizza pizza=new Pizza ();为了让系统有弹性,我们很希望这是一个抽象类或接口,但如果这样,这些类或接口就无法实例化
          pizza.prepare();
          pizza.bake();
          pizza.cut();
          pizza.box();
           return pizza;
    }
    但是你需要更多的pizza类型,所以你增加一些代码,来“决定合适的pizza类型”,然后在制造pizza。
     
    Pizza orderPizza(String type){ 现在把pizza类型传入
          Pizza pizza;
           if(type.equals("cheess" )){
                pizza= new CheessPizza(); 注意这里的具体pizza类型都必须实现Pizza接口
          } else if (type.equals("greek")){
                pizza= new GreekPizza();
          }
          . . .
          pizza.prepare(); 每个子类都知道如何准备自己
          pizza.bake();
          pizza.cut();
          pizza.box();
           return pizza;
    }
     
    但是压力来自于增加更多的pizza类型
     
      我们想要增加一些新类型的pizza和删除一些旧类型的pizza,就必须修改以上代码。
     很明显地,如果实例化“某些“具体类,将使orderPizza()出问题,而且也无法让orderPizza对修改关闭,但是,现在我们已经知道哪些会改变,哪些不会改变,该是使用封装的时候了。
     
    封装创建对象的代码
     现在最好将创建对象移到orderPizza之外,但怎么做呢?这个嘛!要把创建pizza的代码移到另一个对象中,由这个新对象专职创建pizza。
     
    pizza orderPizza(String type){
          Pizza pizza;
          原先的创建对象的代码已经从该方法中抽离,
          这里该怎么写呢?
          pizza.prepare();
          pizza.bake();
          pizza.cut();
          pizza.box();
           return pizza;
    }
     
    把原先的创建对象的代码移到新对象中,如果任何对象想要创建pizza,找这个新对象就对了。
      我们称这个新对象为”工厂“。
     工厂(factory)处理创建对象的细节。一旦有了SimplePizzaFactory,orderPizza()就变成了此对象的客户。当需要pizza时,就叫pizza工厂做一个。那些orderpizza()需要知道pizza类型的日子一去不复返了。现在orderPizza()只关心从工厂得到了一个pizza,而这个pizza实现了Pizza接口,所以他可以调用prepare(),bake()等。
     还有一些细节,比方说,原先在orderPizza()方法中创建代码,现在怎么写? 现在我们来实现一个简单的pizza factory。
     先从工厂本身开始,我们要定义一个类,为所有的pizza创建对象的代码,代码向这样:
    public class SimplePizzaFactory //这是一个新类,他只做一件事:帮他的客户创建 pizza
    {
           public Pizza createPizza(String type) { //在这个工厂中内定了这个方法,所以客户用这个方法来实例化新对象
                 Pizza pizza = null;
     
                 if (type.equals("cheese" )) {
                      pizza = new CheesePizza();
                } else if (type.equals( "pepperoni")) {
                      pizza = new PepperoniPizza();
                } else if (type.equals("clam")) {
                      pizza = new ClamPizza();
                } else if (type.equals("veggie")) {
                      pizza = new VeggiePizza();
                }
                 return pizza;
          }
    }
     
    这样做有什么好处?似乎只是把问题搬到另一个对象罢了,问题依然存在。
     答:别忘了,
    SimplePizzaFactory 有许多的客户,虽然目前只看到orderpizza方法是他的客户,然后,可能还有pizzashopMenu(pizza店菜单)类,会利用这个工厂来取得pizza的价钱和描述。可能还有一个HomeDelivery(宅急送)类,会以与PizzaShop类不同的方式来处理Pizza。总而言之,这个类可以有很多的客户。
     
     所以,把创建pizza的代码包装进一个类,当以后实现改变时,只需修改这个类即可。
      别忘了,我们也正要把具体实例化的过程,从客户的代码中删除!
     
    问:我曾看到一个类似的设计方式,把工厂定义为一个静态的方法,这有何差别?
    答:利用静态方法定义一个简单的工厂,这是很常见的技巧,常称为静态工厂。为何使用静态方法?因为不需要使用创建对象的方法来实例化对象。但请记住,这样也有缺点,不能通过继承来改变创建方法的行为。
     
    重做PizzaStore类
     是修改客户代码的时候了,我们要做的是仰仗工厂来为我们创建pizza。
    public class PizzaStore {
     
          SimplePizzaFactory factory;// 为PizzaStore加上SimplePizzaFactory的引用
           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;
          }
          
           //这里是其他方法
     
    }

     
    定义简单工厂
      简单工厂其实不是一个设计模式,反而更像一种编程习惯,但由于经常被使用,所以我们给他一个”Head First Pattern 荣誉奖“,有些开发人员的确是把这个编程习惯误认为是”工厂模式“,当你下次和另一个开发人员无话可说的时候,这应该是打破沉默的一个不错的话题。
     
     不要因为简单工厂不是一个”真正的“模式,就忽略的它的用法,让我们来看看新的Pizza类图:、
     

    加盟披萨店

     对象村披萨店经营有成,很多人都想加盟。身为加盟公司的经营者,希望确保加盟店营运的质量,所以希望这些店都是用你那些经过时间考验的代码。

     但是区域的差异呢?每家加盟店可能想要提共不同风味的pizza(比方说,纽约,芝加哥,加州)。这受到了开店地点及pizz美食家口味的影响。

     

     我们按照原先的思路做,利用SimplePizzaFactory,写出三种不同的工厂,那么各地加盟店都有合适的工厂可以使用。这是一种做法。

     但是,你想要更多控制。。。。

      在推广SimpleFactory时,你会发现加盟店的确实采用你的工厂创建pizza,但是其他部分,却开始采用他们自创的流程:烘烤的做法有些差异,不要切片,使用其他厂商的盒子等。

      在想想这个问题,你真的希望能够建立一个框架,把加盟店和创建pizza绑在一起的同时又保持一定的弹性。

     在我们稍早的SimplePizzaFactory代码之前,制作pizza的代码绑在PizzaStore里,但这么做却没有弹性。那么,该如何做才能鱼与熊掌兼得呢?

    给披萨店使用的框架

      有个做法可以让披萨制作活动局限于PizzaStore类,而同时又能让这些加盟店依然可以自由地制作该区域的风味。

      所要做的事情,就是把createPizza()方法放回到PizzaStore中,不过要把它设置成”抽象方法“,然后为每个区域风味创建一个PizzaStore的子类。

     首先,让我们来看看PizzaStore所做的改变。

    public abstract class PizzaStore{
        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);
     }

    在PizzaStore里,工厂方法现在是抽象的。

     现在已经有了一个PizzaStore作为超类,让每个域类型(NYPizzaStore,ChicagePizzaStore,CaliForniaPizzaStore)都继承这个PizzaStore,每个子类各自决定如何制造Pizza,让我们看看这要如何进行。

    允许子类做决定

     别忘了,PizzaStore已经有一个不错的订单系统,由orderPizza()方法负责处理订单,而你希望所有加盟店对于订单的处理都能够一致。

    各个区域pizz之间的差异在于他们制作pizza的风味,我们现在要让createPizza()能够应对这些变化来负责创建正确种类的pizza。做法是让PizzaStore的各个子类负责定义自己的createPizza方法,所以我们会得到一些PizzaStore具体的子类。

     

     我不明白,毕竟PizzaS的子类终究只是子类,如何能做决定?

     关于这个,我们要从PizzaS的orderPizza()方法来看,此方法是抽象的PizzaStore内定义,但是只是在子类中实现具体类型。

     PizzaStore

    createPizza()

    orderPizza();

    现在,更进一步地,orderPizza()方法对Pizza对象做了许多事情,(如bake,cut等),但由于Pizza对象是抽象的,orderPizza并不知道哪些实际的具体类参与进来了。换句话说:就是解耦decouple

    让我们开一家Pizza  Store吧

      开加盟店有他的好处,可以从PizzaStore免费获得所有的功能,区域点只需要继承PizzaStore,然后提供createPizza()方法实现自己的Pizza风味即可。

    这是纽约风味:

    public class NYPizzaStore extends PizzaStore{
        Pizza createPizza(String item) {
            if (item.equals("cheese")) {
                return new NYStyleCheesePizza();
            } else if (item.equals("veggie")) {
                return new NYStyleVeggiePizza();
            } else if (item.equals("clam")) {
                return new NYStyleClamPizza();
            } else if (item.equals("pepperoni")) {
                return new NYStylePepperoniPizza();
            } else return null;
        }
    }

    其他的2个类型的PizzaStore类似。

     声明一个工厂方法

     原本是由一个对象负责所有具体类的实例化,现在通过对PizzaStore做一些小转变,变成由一群子类来负责实例化,让我们看的仔细些:

    public abstract class PizzaStore {
     
        abstract Pizza createPizza(String item); 
     
        public Pizza orderPizza(String type) {
            Pizza pizza = createPizza(type);
            System.out.println("--- Making a " + pizza.getName() + " ---");
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    }
    abstract Pizza createPizza(String item);实例化披萨的责任被移到了一个“方法”中,此方法就如同一个工厂。

    工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样,客户程序中关于超类的代码就和子类对象创建代码解耦了

    abstract Product factoryMethod(String type);

    abstract:工厂方法是抽象的,所以依赖子类来处理对象的创建。
    Product:工厂方法必须返回一个产品,超类中定义的方法,通常使用到工厂方法的返回值。


    看看如何订购Pizza

    各个类的代码:

    public abstract class Pizza {
        String name;
        String dough;
        String sauce;
        ArrayList toppings=new ArrayList();
        
        void prepare(){
            System.out.println("Preparing " + name);
            System.out.println("Tossing dough...");
            System.out.println("Adding sauce...");
            System.out.println("Adding toppings: ");
            for (int i = 0; i < toppings.size(); i++) {
            System.out.println("  "+toppings.get(i));
            }
            
        }
        void bake() {
            System.out.println("Bake for 25 minutes at 350");
        }
     
        void cut() {
            System.out.println("Cutting the pizza into diagonal slices");
        }
      
        void box() {
            System.out.println("Place pizza in official PizzaStore box");
        }
     
        public String getName() {
            return name;
        }

    注意Pizza类代码,我们特意用了abstract,虽然里面没有abstract方法,我们不想让他实例化

    public class NYStyleCheesePizza extends Pizza{
        public NYStyleCheesePizza(){
            name="NY Style Sauce and cheese Pizza";
            dough="Thin Crust Dough";
            sauce="Marinara sauce";
            
            toppings.add("Grated Reggiano Cheese");
        }
        
    }
    public class NYStyleVeggiePizza extends Pizza {
    
        public NYStyleVeggiePizza() {
            name = "NY Style Veggie Pizza";
            dough = "Thin Crust Dough";
            sauce = "Marinara Sauce";
     
            toppings.add("Grated Reggiano Cheese");
            toppings.add("Garlic");
            toppings.add("Onion");
            toppings.add("Mushrooms");
            toppings.add("Red Pepper");
        }
    }
    public abstract class PizzaStore {
        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 class NYPizzaStore extends PizzaStore{
        Pizza createPizza(String item) {
            if (item.equals("cheese")) {
                return new NYStyleCheesePizza();
            } else if (item.equals("veggie")) {
                return new NYStyleVeggiePizza();
            } 
            
            else 
                return null;
        }
    }

    测试类:

    public class PizzaTestDrive {
     
        public static void main(String[] args) {
            PizzaStore nyStore = new NYPizzaStore();
            PizzaStore chicagoStore = new ChicagoPizzaStore();
     
            Pizza pizza = nyStore.orderPizza("cheese");
            System.out.println("Ethan ordered a " + pizza.getName() + "\n");
     
            pizza = chicagoStore.orderPizza("cheese");
            System.out.println("Joel ordered a " + pizza.getName() + "\n");
    
     }
    }

    认识工厂方法模式

     所有工厂模式都用了封装对象的创建,工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。让我们来看看这些类图:

    另一个观点:平行的类层级

     我们看到,将一根orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架,除此之外,工厂方法将生产知识封装进各个创建者,这样的做法,也可以被视为一个框架。

     让我们看看这两个平行的类层级,

    定义工厂方法模式

     定义了一个创建对象的接口,但由子类决定要实例化的类时哪一个,工厂方法让类把实例化推迟到子类

    上面的图值得仔细看看。

    问:工厂方法和创建者是否总是抽象的?

    不?可以定义一个默认的工厂方法来产生一些具体的产品,这么一来,即使创建者没有任何子类,依然可以创建产品。

    一个很依赖的披萨店

     下面是一个不使用工厂模式的pizzaStore版本,数一下,这个类所依赖的具体披萨对象有几种。如果又加了一种加州风味的Pizza到这个店,那么届时又会依赖几个对象?

    public class DependentPizzaStore {
     
        public Pizza createPizza(String style, String type) {
            Pizza pizza = null;
            if (style.equals("NY")) {
                if (type.equals("cheese")) {
                    pizza = new NYStyleCheesePizza();
                } else if (type.equals("veggie")) {
                    pizza = new NYStyleVeggiePizza();
                } else if (type.equals("clam")) {
                    pizza = new NYStyleClamPizza();
                } else if (type.equals("pepperoni")) {
                    pizza = new NYStylePepperoniPizza();
                }
            } else if (style.equals("Chicago")) {
                if (type.equals("cheese")) {
                    pizza = new ChicagoStyleCheesePizza();
                } else if (type.equals("veggie")) {
                    pizza = new ChicagoStyleVeggiePizza();
                } else if (type.equals("clam")) {
                    pizza = new ChicagoStyleClamPizza();
                } else if (type.equals("pepperoni")) {
                    pizza = new ChicagoStylePepperoniPizza();
                }
            } else {
                System.out.println("Error: invalid type of pizza");
                return null;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
            return pizza;
        }
    }

    看看对象依赖

     你直接实例化一个对象是,就是在依赖他的具体类

    我们把这个版本的披萨店和他依赖的对象画成一张图,应该是这样:

      很清楚地,代码里减少对于具体类的依赖是件“好事”,事实上,有一个oo设计原则就正式阐明了这一点;这个

    原则叫做:依赖倒置原则(dependency inversion principle)

    通则如下:

      要依赖抽象,不要依赖具体类。

     首先,这个原则听起来很像是“针对接口编程,不针对实现编程”,不是吗?的确很像是,然而这里更强调“抽象”。

    这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象

    让我们看看DependentPizzaStore图,PizzaStore是“高层组件”,而披萨实现的是“低层组件”,很清楚地,PizzaStore依赖这些具体类。

     现在,这个原则告诉我们,应该重写代码以便于我们依赖抽象类,而不依赖具体类。对于高层及低层模块都应该如此。

      但是怎么做呢?我们来想想看怎样在“非常依赖披萨店”实现中,应用这个原则 。。。

    原则的应用

      非常依赖披萨店的问题在于:它依赖每个披萨类型,因为他是在自己的orderPizza()方法中,实例化这些具体类的。

     虽然我们创建了一个抽象,也就是Pizza,但我们任然在代码中,实际地创建了具体的Pizza,所以,这个抽象没什么影响力。

     如何在orderPizza()方法中,将这些实例化对象的代码独立出来?我们都知道,工厂方法刚好可以派上用场。

     所以,应用工厂方法后,类图看起来像这样:

     在应用工厂方法之后,你将注意到,高层组件(也就是PizzaStore)和低层组件(也就是这些Pizza)都依赖了Pizza抽象,想要遵循依赖倒置原则,工厂方法并非是唯一的技巧,但却是最有威力的技巧之一。

    究竟倒置在哪里?

     在依赖倒置原则中的倒置指的是和一般oo设计的思考方式完全相反。看看前一页的图,低层组件现在竟然依赖高层的抽象,同样第,高层组件现在也依赖相同的抽象。前几页所绘制的依赖图是由上到下的,现在却倒置了。而且高层和低层现在都依赖这个抽象。

    几个指导方针帮助你遵循依赖倒置原则

    1.变量不可以持有具体类的引用。 

          (如果使用new,就会持有具体类的引用,你可以改用工厂方法来避开这样的做法。)

    2.不要让类派生自具体类。

       (如果派生自具体类,你就会依赖具体类,请派生自一个抽象(接口或抽象类)。

    3不要覆盖基类中已实现的方法。

      (如果覆盖基类中以实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。)

     但是,等等,要完全遵守这些规则,那么我连一个简单的程序都写不出来!

     你说的没错。正如同我们的许多原则一样,应该尽量达到这个原则,而不是随时都要遵循这个原则。

    但是,如果深入体验这些方针,将这些方针内化成你思考的一部分,那么在设计时,你将知道何时有足够的理由违反这样的原则。比方说。如果有一个不像是会改变的类,那么在代码中直接实例化具体类也就没什么障碍。想想看,我们平常还不是在程序中不假思索的i实例化字符串对象吗?就没有违法这个原则?当然有!可以这么做嘛?可以!为什么,因为字符串不可能改变。

     另一方面,如果某个类可能改变,你可以采用一些好的技巧(如工厂方法)来封装改变。





     
     
     
     
  • 相关阅读:
    CMake 手册详解(五)
    linux 学习资料、Linux学习书籍(入门书籍、shell编程)推荐
    linux shell 管道命令(pipe)使用及与shell重定向区别
    linux shell “(())” 双括号运算符使用
    web签名验证程序【跨服务器、中文字符签名方法】php为例
    linux shell 脚本实现tcp/upd协议通讯(重定向应用)
    web程序乱码深入分析【基础原理篇】php为例
    php empty,isset,is_null比较(差异与异同)
    php 实现进制转换(二进制、八进制、十六进制)互相转换
    php通过文件头检测文件类型通用类(zip,rar…)
  • 原文地址:https://www.cnblogs.com/youxin/p/2681212.html
Copyright © 2020-2023  润新知