加盟披萨店
对象村披萨店经营有成,很多人都想加盟。身为加盟公司的经营者,希望确保加盟店营运的质量,所以希望这些店都是用你那些经过时间考验的代码。
但是区域的差异呢?每家加盟店可能想要提共不同风味的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实例化字符串对象吗?就没有违法这个原则?当然有!可以这么做嘛?可以!为什么,因为字符串不可能改变。
另一方面,如果某个类可能改变,你可以采用一些好的技巧(如工厂方法)来封装改变。