Duck duck = new MallardDuck();
当有一群相关的具体类时,通常会写出这样的代码:
Duck duck;
if(picnic){
duck = new MallardDuck();
}else if(hunting){
duck = new DecoyDuck();
}else if(inBathTub){
duck = new RubberDuck();
}
有一大堆不同的鸭子类,但是必须等到运行时,才知道该实例化是哪一个。
这样的代码,一旦有变化或扩展,就必须重新打开这段代码进行检查和修改。通常这样修改过的代码将造成部分系统更难维护和更新,而且也更容易犯错。
在技术上,new没有错,毕竟这是java的基础部分。真正的犯人是我们的老朋友“改变”,以及它是如何影响new的使用的。
上述代码一旦加入新的具体类,就必须改变代码。也就是说,代码并非是“对扩展开放,对修改关闭”。
假设你有一个比萨店,身为对象村内最先进的比萨店主人,代码可能这么写:
Pizza orderPizza(){
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但是需要更多的比萨类型:
Pizza orderPizza(String type){
Pizza pizza;
if("cheese".equals(type)){
pizza = new CheesePizza();
}else if("greek".equals(type)){
pizza = new GreekPizza;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但是压力来自于增加更多的比萨类型:
if else相关实例化的代码是变化的部分。随着时间过去,比萨菜单改变,这里就必须一改再改。此代码没有对修改关闭。
很明显,如果实例化“某些”具体类,将使orderPizza()出问题,而且也无法让orderPizza()对修改关闭;
现在我们已经知道哪些会改变,哪些不会改变,该是使用封装的时候了。
现在最好将创建对象移到orderPizza()之外,移到另一个对象中,由这个新对象专职创建比萨。
我们称这个新对象(SimplePizzaFactory)为“工厂”。
一旦有了SimplePizzaFactory,orderPizza()就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。
public class SimplePizzaFactory(){
Pizza pizza;
if("cheese".equals(type)){
pizza = new CheesePizza();
}else if("greek".equals(type)){
pizza = new GreekPizza;
}
return pizza;
}
这个代码还是根据类型创建,代码没有实质性变动。
重做PizzaStore类:
pulic class PizzaStore{
//添加工厂的引用
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
//披萨来自于工厂
Pizza pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
简单工厂定义:
比萨店经营有成,击败了竞争者,现在需要有加盟店。身为加盟公司经营者,你希望确保加盟店营运的质量,所以希望这些店都使用你那些经过时间考验的代码。
但是区域的差异呢?每家加盟店都可能想要提供不同风味的比萨(比如纽约、芝加哥),这受到了开店地点及该地区比萨美食家口味的影响。
如果利用SimplePizzaFactory,写出三种不同的工厂,分别是NYPizzaFactory、ChicagoPizzaFactory那么各地加盟店都有适合的工厂使用,代码如下:
//纽约风味
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.orderPizza("Veggie");
//芝加哥风味
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore nyStore = new PizzaStore(chicagoFactory);
nyStore.orderPizza("Veggie");
推广SimpleFactory时,你发现加盟店的确是采用你的工厂创建比萨,但是其他部分,却开始采用他们自创的流程:做法差异、不要切片,使用其他厂商的盒子。
制作比萨的代码绑在PizzaStore里,但这么做却没有弹性。有个做法(抽象)可以让比萨制作活动局限于PizzaStore类,而同时又能让这些加盟店依然可以自由地制作该区域的风味。
具体做法是:把createPizza()方法放回到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);
}
允许子类做决定:
工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码就和子类对象创建代码解耦了。
abstract Porduct factoryMethod(String type)
如果没有比萨可出售,我们的比萨店开得再多也不行,现在我们来实现比萨:
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
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza(){
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
}
@Override
void cut(){
System.out.println("Cutting the pizza into square slices");
}
}
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza(){
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crest Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
所有工厂模式都是用来封装对象的创建。工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架。除此之外,工厂方法将生产知识封装进各个创建者,这样的做法,也可以被视为是一个框架。
Pizza和PizzaStore两个类层级是平行的:因为它们都有抽象类,而抽象类都有许多具体的子类,每个子类都有自己特定的实现。
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
依赖倒置原则:要依赖抽象,不要依赖具体类。
这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象。
所谓“高层”组件,是由其他低层组件定义其行为的类。例如,PizzaStore是个高层组件,因为它的行为是由比萨定义的:PizzaStore创建所有不同的比萨对象,准备、烘烤、切片、装盒;而比萨本身属于低层组件。
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
抽象工厂的每个方法实际上看起来都像是工厂方法(例如:createDough()、createSource()等)。每个方法都被声明成抽象,而子类的方法覆盖这些方法来创建某些对象,这不正是工厂方法吗?