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

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





 
 
 
 
原文地址:https://www.cnblogs.com/youxin/p/2681212.html