上一篇:《设计模式03—装饰者模式》
4.工厂模式
针对接口编程,可以隔离掉以后系统可能发生的一大堆改变。
为什么呢?如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体类时,等于是自找麻烦,因为一旦加入新的具体类就必须改变代码。
也就是说,你的代码并非关闭”。想用新的具体类型来扩展代码,必须重新打开它。
所以当遇到这样的问题时,该怎么办?,就应该回到OO设计原则去寻找线索。我们的第一个原则用来处理改变,并帮助我们“找出会变化的方面,把它们从不变的部分分离出来”。
针对这个问题我们开始识别变化的方面
假设我们有一个披萨店,我们对于订购披萨的代码可能如下所示:
public Pizza OrderPizza() {
Pizza pizza=new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
为了让系统有弹性,我们很希望这是一个抽象类或接口。但是如果这样,这些类或接口就无法直接实例化。
但是我们需要更多的披萨类型,所以必须增加一些代码来决定适合的披萨类型,在进行制造:
public Pizza OrderPizza(String type) {
Pizza pizza=new Pizza();
if(type.equals("cheese")){
pizza=new CheesePizza();
}else if(type.equals("greek")){
pizza=new GreekPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
这种代码在实际使用中,一旦新增或者去掉一种披萨,那么我们的代码就要重写,比如由于芝士披萨卖的不好,我们需要从菜单中去除,所以代码就会以下所示:
public Pizza OrderPizza(String type) {
Pizza pizza=new Pizza();
if(type.equals("greek")){
pizza=new GreekPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但是始终保持不变的还是披萨的prepare,bake,cut,box
所以我们将变化的提取出来,然后把这个变化的部分搬到另外一个对象中,这个新对象只负责如何创建披萨,我们称这个新对象为“工厂”,于是我们的工厂模式就应运而生了,为了后面方便叙述,我们把这个新对象命名为SimplePizzaFactory。
工厂(factory)处理创建对象的细节。一旦有了SimplePizzaFactory ,orderPizza()就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。那些orderPizza()方法需要知道希腊比萨或者蛤螂比萨的日子一去不复返了。现在orderPizza()方法只关心从工厂得到了一个比萨,而这个比萨实现了Pizza接口,所以它可以调用prepare(),bake(),cut(),box()来分别进行准备、烘烤、切片、装盒。
下面我们先建立一个简单的披萨工厂
先从工厂本身开始。我们要定义一个类,为所有比萨封装创建对象的代码。代码像这样……
/**
* 它只做一件事,帮他的客户创建披萨
*/
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoniPizza")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}
接下来我们重做PizzaStroe类
public class PizzaStore {
SimplePizzaFactory factory;
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;
}
}
定义简单工厂
简单工厂其实不是一个设计模式,反而比较像是种编程习惯
不要因为简单工厂不是一个“真正的”模式,就忽略了它的用法。让我们来看看新的比萨店类图:
注意:在设计模式中,所调的“实现一个接口“不一定”表示“写一个类,并利用implements关键词来实现某个接口,实现接口泛指实现某个超类型(可以是类也可以是接口)的某个方法
加盟披萨店
每家加盟店都可能想要提供不同风味的比萨(比方说纽约、芝加哥、加州),这受到了开店地点及该地区比萨美食家口味的影响。
我们如何去做呢?
首先先看看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作为超类;
让每个域类型(NYPizzaStore,ChicagoPizzaStore、CaliforniaPizzaStore)都继承这个PizzaStore,每个子类各自决定如何制造比萨。
现在,更进一步地,orderPizza()方法对Pizza对象做了许多事情(例如:准备、烘烤、切片、装盒),但由于Pizza对象是抽象的,orderPizza()并不知道哪些实际的具体类参与进来了。换句话说,这就是解耦(decouple)!
当orderPizza()调用createPizza()时,某个比萨店子类将负责创建比萨。
做哪一种比萨呢?
当然是由具体的比萨店来决定
那么,子类是实时做出这样的决定吗?
不是,但从orderPizza()的角度来看,如果选择在NYStylePizzaStore订购比萨,就是由这个子类(NYStylePizzaStore)决定。
严格来说,并非由这个子类实际做“决定”,而是由“顾客”决定到哪一家风味的比萨店才决定了比萨的风味。
于是我们一次看看NYPizzaStore(纽约风味的披萨)长什么样子吧
/**
* 纽约风格的披萨商店
*/
public class NYPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (type.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (type.equals("clam")) {
return new NYStyleClamPizza();
} else if (type.equals("pepperoni")) {
return new NYStylePepperoniPizza();
}
return null;
}
}
- Pizza
/** * 披萨的基类 */ abstract public class Pizza { String name;//披萨的名称 String dough;//披萨的所用的面料 String sauce;//披萨所用的酱料 List<String> toppings = new ArrayList<String>(); public String getName() { return name; } //准备披萨 public void prepare() { System.out.println("Preparing " + name); } //烘烤披萨 public void bake() { System.out.println("Baking " + name); } //裁剪披萨 public void cut() { System.out.println("Cutting " + name); } //打包披萨 public void box() { System.out.println("Boxing " + name); } public String toString() { // code to display pizza name and ingredients StringBuffer display = new StringBuffer(); display.append("---- " + name + " ----\n"); display.append(dough + "\n"); display.append(sauce + "\n"); for (String topping : toppings) { display.append(topping + "\n"); } return display.toString(); } }
- NYStyleCheesePizza
/** * 纽约风味的芝士披萨 */ public class NYStyleCheesePizza extends Pizza { public NYStyleCheesePizza() { name = "纽约风味的芝士披萨"; dough = "薄皮面团"; sauce = "蕃茄酱"; toppings.add("雷吉亚诺奶酪"); } }
- NYStyleVeggiePizza
/** * 纽约风味的蔬菜披萨 */ public class NYStyleVeggiePizza extends Pizza { public NYStyleVeggiePizza() { name = "纽约风味的蔬菜披萨"; dough = "薄皮面团"; sauce = "番茄酱"; toppings.add("奶酪"); toppings.add("大蒜"); toppings.add("大葱"); toppings.add("蘑菇"); toppings.add("红辣椒"); } }
- NYStyleClamPizza
/** * 纽约风味的蛤蜊披萨 */ public class NYStyleClamPizza extends Pizza { public NYStyleClamPizza() { name = "纽约风味的蛤蜊披萨"; dough = "薄皮面团"; sauce = "番茄酱"; toppings.add("雷吉亚诺奶酪"); toppings.add("来自长岛海峡的新鲜蛤蜊"); } }
- NYStylePepperoniPizza
/** * 纽约风味的意大利香肠披萨 */ public class NYStylePepperoniPizza extends Pizza { public NYStylePepperoniPizza() { name = "纽约风味的意大利香肠披萨"; dough = "薄皮面团"; sauce = "咖喱酱"; toppings.add("奶酪"); toppings.add("香肠"); toppings.add("大蒜"); toppings.add("洋葱"); toppings.add("蘑菇"); toppings.add("红辣椒"); } }
接下来我们继续实现芝加哥的披萨店
/**
* 芝加哥的披萨店
*/
public class ChicagoPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new ChicaoStyleCheesePizza();
} else if (type.equals("veggie")) {
return new ChicaoStyleVeggiePizza();
} else if (type.equals("clam")) {
return new ChicaoStyleClamPizza();
} else if (type.equals("pepperoni")) {
return new ChicaoStylePepperoniPizza();
}
return null;
}
}
自己可以模仿着纽约风味的披萨,写出ChicaoStyleCheesePizza,ChicaoStyleVeggiePizza,ChicaoStylePepperoniPizza,就像下面的ChicaoStyleCheesePizza一样
/**
* 芝加哥风味的芝士披萨
*/
public class ChicaoStyleCheesePizza extends Pizza {
public ChicaoStyleCheesePizza() {
name = "芝加哥风味的芝士披萨";
dough = "厚皮面团";
sauce = "蕃茄酱";
toppings.add("雷吉亚诺奶酪");
}
}
工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码就和子类对象创建代码解耦了。
接下来我们回顾一下订单是如何生成披萨的,假如我们需要订购一款纽约风味的芝士披萨
-
1.首先我们需要一个纽约的披萨店
PizzaStore nyPizzaStore=new NYPizzaStore();
-
2.现在有一个店了,那么我们就可以下订单了
nyPizzaStore.orderPizza("cheese");
调用nyPizzaStoze实例的ordePiza方法(达个方法被定义在PizzeStore中)。
-
3.ordePiza方法调用createPizza方法;
-
4.最后,披萨必须经过烘焙,裁剪,打包等才算完成
pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box();
接下来我们正式订购一个披萨
public class Store {
public static void main(String[] args) {
//首先建立商店
PizzaStore nyPizzaStore = new NYPizzaStore();
PizzaStore chicaoStore = new ChicagoPizzaStore();
Pizza pizza = nyPizzaStore.orderPizza("cheese");
System.out.println("---------纽约风味的芝士披萨制作完成:"+pizza.toString()+"---------------------");
pizza = chicaoStore.orderPizza("cheese");
System.out.println("---------芝加哥风味的芝士披萨制作完成:"+pizza.toString()+"---------------------");
}
}
运行结果如下:
认识工厂方法模式
所有工厂模式都用来封装对象的创建。
工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。让我们来看看这些类图,以了解有哪些组成元素:
-
1.创建者类
-
2.产品类
平行的类层级
我们已经看到,将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架。除此之外,工厂方法将生产知识封装进各个创建者,这样的做法,也可以被视为是一个框架。
定义工厂方法模式
工厂方法模式能够封装具体类型的实例化。如下面的类图所示,抽象的Creator提供了一个创建对象的方法的接口,也称为“工厂方法”。在抽象的Creator中,任何其他实现的方法,都可能使用到这个工厂方法所制造出来的产品,但只有子类真正实现这个工厂方法并创建产品。
工厂方法让子类决定要实例化的类是哪一个。
希望不要理解错误,所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品是什么。
- 当只有一个ConcreteCreator的时候,工厂方法模式有什么优点?
尽管只有一个具体创建者,工厂方法模式依然很有用,因为它帮助我们将产品的“实现”从“使用”中解耦。如果增加产品或者改变产品的实现,Creator并不会受到影响(因为Creator与任何ConcreteProduct之间都不是紧耦合)。
对象依赖,依赖倒置原则
当我们直接实例化一个对象时,就是在依赖它的具体类
何为依赖倒置原则:
要依赖抽象,不要依赖具体类
首先,这个原则听起来很像是“针对接口编程,不针对实现编程”,然而这里更强调“抽象”。
这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象。
所调“高层”组件.是由其他低层组件定义其行为的类。例如,PizzaStore是个高层组件.团为它的行为是由披萨定义的:PixzaStore创建所有不同的比萨对象,准备、烘烤、切片、装盒;而披萨本属于低层组件。
所以我们应用工厂模式之后,我们的类图应该更像下图所示:
在应用工厂方法之后,你将注意到,高层组件(也就是PizzaStore)和低层组件(也就是这些比萨)都依赖了Pizza抽象。想要遵循依赖倒置原则,工厂方法并非是唯的技巧,但却是最有威力的技巧之一。
下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:
- 1.变量中不可以持有具体类的引用。
如果使用New,就会持有具体类的引用。你可以改用工厂来避开这样的做法
- 2.不要让类派生自具体类。
如果派生自具体类,那么我们就会依赖具体类,请派生自一个抽象(接口或抽象类)
- 3.不要覆盖基类中已经实现的方法
如果覆盖基类已经=实现的方法,那么我们的基类就不是一个真正适合被继承的抽象。基类中已经实现的方法,应该由所有的子类共享。
接下来我们继续回到披萨店,为了保证披萨口感,我们需要建立一个原料工厂,来确保原料的一致,下面是纽约和芝加哥的两组不同的原料
建造原料工厂
现在,我们要建造一个工厂来生产原料;这个工厂将负责创建原料家族中的每一种原料。也就是说,工广将需要生产面团、酱料、芝士等。待会儿,你就会知道如何处理各个区域的差异了。
开始先为工厂定义一个接口,这个接口负责创建所有的原料:
/**
* 原料工厂
*/
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
这里有许多的新类,每一个原料都是一个类
而我们要做的事情就是:
- 1.为每个区域建造一个工厂。你需要创建一个继承自PizzaIngredientFactory的子类来实现每一个创建方法。
- 2.实现一组原料类供工厂使用,例如ReggianoCheese、RedPeppers、ThickCrust-Dough。这些类可以在合适的区域间共享。
- 3.然后你仍然需要将这一切组织起来,将新的原料工厂整合进旧的PizzaStore代码中
对应的Dough,Sauce,Cheese,Veggies,Pepperoni,Clams如下 - 1.Dough
/** * 面团 */ public interface Dough { public String toString(); }
- 2.Sauce
/** * 酱 */ public interface Sauce { String toString(); }
其他的几个抽象接口也如上面两个一样,自己可以尝试编写
接下来我们实现纽约原料工厂,该工厂精于大蒜番茄酱料,Reggiano干酪,新鲜蛤蜊等
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = {
new Garlic(), new Onion(), new Mushroom(), new RedPepper()
};
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClam() {
return new FeshClams();
}
}
其中ThinCrustDough,MarinaraSauce等如下:
- ThinCrustDough
public class ThinCrustDough implements Dough{ @Override public String toString() { return "薄皮面团"; } }
- MarinaraSauce
public class MarinaraSauce implements Sauce{ @Override public String toString() { return "蕃茄酱"; } }
剩下的自己去编写即可,本文就不过多的赘述
接下来我们去实现芝加哥的食料工厂
/**
* 芝加哥风味的食料工厂
*/
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThickCrustDough();
}
@Override
public Sauce createSauce() {
return new PlumTomatoSauce();
}
@Override
public Cheese createCheese() {
return new MozzarellaCheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = {new BlackOlives(),
new Spinach(),
new Eggplant()};
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClam() {
return new FrozenClams();
}
}
对应的ThickCrustDough,PlumTomatoSauce如下:
- ThickCrustDough
public class ThickCrustDough implements Dough { @Override public String toString() { return "厚皮面团"; } }
- 2.PlumTomatoSauce
public class PlumTomatoSauce implements Sauce{ @Override public String toString() { return "番茄酱配李子番茄"; } }
其他的自己可以仿照着自己去编写
工厂均准备就绪,接下来我们需要重做披萨,让它只使用工厂生产出的原料
/**
* 改版之后的披萨,只能使用我们工厂的原料
*/
public abstract class Pizza02 {
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;
abstract void prepare();
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");
}
void setName(String name) {
this.name = name;
}
String getName() {
return name;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("---- " + name + " ----\n");
if (dough != null) {
result.append(dough);
result.append("\n");
}
if (sauce != null) {
result.append(sauce);
result.append("\n");
}
if (cheese != null) {
result.append(cheese);
result.append("\n");
}
if (veggies != null) {
for (int i = 0; i < veggies.length; i++) {
result.append(veggies[i]);
if (i < veggies.length - 1) {
result.append(", ");
}
}
result.append("\n");
}
if (clam != null) {
result.append(clam);
result.append("\n");
}
if (pepperoni != null) {
result.append(pepperoni);
result.append("\n");
}
return result.toString();
}
}
继续重做披萨
现在已经有了一个抽象比萨,可以开始创建纽约和芝加哥风味的比萨了。从今以后,加盟店必需直接从工厂取得原料,那些偷工减料的日子宣告结束了!
我们曾经写过工厂方法的代码,有NYCheesePizza和ChicagoCheesePizza类。比较一下这两个类,唯一的差别在于使用区域性的原料,至于比萨的做法都一样(面团+酱料+芝士),其他的比萨(蔬菜、蛤蝴等)也是如此。它们都依循着相同的准备步骤,只是使用不同的原料。
所以,其实我们不需要设计两个不同的类来处理不同风味的比萨,让原料工厂处理这种区域差异就可以了。下面是CheesePizza :
/**
* 只能使用自己工厂的原料的芝士披萨
*/
public class CheesePizza02 extends Pizza02 {
PizzaIngredientFactory pizzaIngredientFactory;
public CheesePizza02(PizzaIngredientFactory pizzaIngredientFactory) {
this.pizzaIngredientFactory = pizzaIngredientFactory;
}
@Override
void prepare() {
//preare方法会一步一步的创建芝士披萨,每当需要什么材料只需向食料工厂去要即可
System.out.println("正在准备:" + name);
dough = pizzaIngredientFactory.createDough();
sauce = pizzaIngredientFactory.createSauce();
cheese = pizzaIngredientFactory.createCheese();
}
}
Pizza的代码利用相关的工厂生产原料。所生产的原料依赖所使用的工厂,Pizza类根本不关心这些原料,它只知道如何制作比萨。现在,Pizza和区域原料之间被解耦,无论原料工厂是在洛基山脉还是在西北沿岸地区,Pizza类都可以轻易地复用,完全没有问题。
接下来我们继续实现蛤蜊披萨
/**
* 只能使用工厂原料制作的蛤蜊披萨
*/
public class ClamPizza02 extends Pizza02 {
PizzaIngredientFactory pizzaIngredientFactory;
public ClamPizza02(PizzaIngredientFactory pizzaIngredientFactory) {
this.pizzaIngredientFactory = pizzaIngredientFactory;
}
@Override
void prepare() {
//preare方法会一步一步的创建芝士披萨,每当需要什么材料只需向食料工厂去要即可
System.out.println("正在准备:" + name);
dough = pizzaIngredientFactory.createDough();
sauce = pizzaIngredientFactory.createSauce();
cheese = pizzaIngredientFactory.createCheese();
}
}
做完以上工作,我们基本完工,我们继续回到披萨店
/**
* 使用工厂原料的披萨商店
*/
public abstract class PizzaStore02 {
public Pizza02 orderPizza(String type) {
Pizza02 pizza;
pizza = createPizza(type);//将创建披萨的方法从工厂中移回
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
abstract Pizza02 createPizza(String type);//把工厂对象移到这个方法中
}
继续构建纽约风味的披萨商店
/**
* 使用工厂原料的纽约风味的披萨超市
*/
public class NYPizzaStore02 extends PizzaStore02 {
@Override
Pizza02 createPizza(String type) {
Pizza02 pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();//纽约店全用到纽约披萨原料工厂
if (type.equals("cheese")) {
pizza = new CheesePizza02(ingredientFactory);
pizza.setName("纽约风味的芝士披萨");
} else if (type.equals("veggie")) {
pizza = new VeggiePizza02(ingredientFactory);
pizza.setName("纽约风味的蔬菜披萨");
} else if (type.equals("clam")) {
pizza = new ClamPizza02(ingredientFactory);
pizza.setName("纽约风味的蛤蜊披萨");
} else if (type.equals("peoperoni")) {
pizza = new PepperoniPizza02(ingredientFactory);
pizza.setName("意大利辣味香肠");
}
return pizza;
}
}
定义抽象工厂模式
下一篇:《设计模式05—单件模式》