zoukankan      html  css  js  c++  java
  • 工厂模式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实例化字符串对象吗?就没有违法这个原则?当然有!可以这么做嘛?可以!为什么,因为字符串不可能改变。

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





     
     
     
     
  • 相关阅读:
    Java Output流写入包装问题
    SpringBoot项目单元测试不经过过滤器问题
    SpringSecurity集成启动报 In the composition of all global method configuration, no annotation support was actually activated 异常
    JWT jti和kid属性的说明
    Maven 排除依赖
    第五章 基因概念的发现
    第三章 孟德尔遗传的拓展
    第二章 孟德尔遗传
    第一章 引言
    GWAS全基因组关联分析
  • 原文地址:https://www.cnblogs.com/youxin/p/2681212.html
Copyright © 2011-2022 走看看