设计模式之工厂模式
简单工厂
代码实现
public class SimplePizzaFactory {
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;
}
}
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;
}
}
类图
工厂方法
定义
**工厂方法模式 **定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码就和子类对象创建代码解耦了。
public abstract class PizzaStore {
// 工厂方法 createPizza
// 创建披萨 -- 子类实现,子类各自决定如何创建披萨
protected abstract Pizza createPizza(String item);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
生产 纽约芝士口味 比萨
public class NYPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String item) {
switch (item) {
case "cheese":
return new NYStyleCheesePizza(); // 纽约芝士口味 比萨
case "veggie":
return new NYStyleVeggiePizza();
case "clam":
return new NYStyleClamPizza();
case "pepperoni":
return new NYStylePepperoniPizza();
default:
return null;
}
}
}
PizzaStore nyStore = new NYPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese"); // orderPizza()方法会调用createPizza()方法
System.out.println("ordered a : " + pizza.getName() + "\n");
工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
创建者类
产品类
工厂方法类图模板
对象依赖
要依赖抽象,不要依赖具体类。
这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,两者都应该依赖于抽象。
比如,这个例子里的PizzaStore是高层组件,而披萨实现是低层组件,很清楚的,PizzaStore依赖这些具体披萨类。
现在,这个原则告诉我们,应该重写代码以便于我们依赖抽象类,而不依赖具体类。对于高层及低层模块都应该如此。
错误示范
正确方式
在应用工厂方法后,高层组件(也就是PizzaStore)和低层组件(也就是这些披萨)都依赖了Pizza抽象。
下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:
- 变量不可以持有具体类的引用
如果使用new,就会持有具体类的引用。你可以改用工厂来避开这样的做法。 - 不要让类派生自具体类
如果派生自具体类,你就会依赖具体类。请派生自一个抽象(接口或抽象类)。 - 不要覆盖基类中已实现的方法
如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。
抽象工厂
定义
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
解决的问题
问题:加盟店坐落在不同的区域,纽约的红酱料和芝加哥的红酱料是不一样的。加盟店之间有相同的产品家族(意式腊肠、酱料、芝士、蔬菜等等),但是制作方式根据区域的不同而有差异。所以对于纽约和芝加哥,你准备了两组不同的原料。
建造原料工厂
public interface PizzaIngredientFactory {
// 在接口中,每个原料都有一个对应的方法创建该原料
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClams();
}
创建纽约原料工厂
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
// 对于原料家族内的每一种原料,我们都提供了纽约的版本
@Override
public Dough createDough() {
return new ThinCrushDough();
}
@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 createClams() {
// 纽约靠海,所以有新鲜的蛤蜊。芝加哥就必须使用冷冻的蛤蜊
return new FreshClams();
}
}
重做披萨
工厂已经一切就绪,准备生成高质量原料了。现在我们只需要重做披萨,好让它们只使用工厂生产出来的原料。我们先从抽象的Pizza类开始:
public abstract class Pizza {
String name;
// 每个披萨都持有一组在准备时会用到的原料
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clams;
// 现在把prepare()方法声明成抽象。在这个方法中,我们需要收集披萨所需的原料,而这些原料当然是来自原料工厂了。
abstract void prepare();
// 其他的方法保持不动
void bake(){
// ...
}
void cut(){
// ...
}
void box(){
// ...
}
}
现在已经有了一个抽象披萨,可以开始创建纽约和芝加哥风味的披萨了。从今以后,加盟店必须直接从工厂取得原料。
其实我们不需要设计两个不同的类来处理不同风味的披萨,让原料工厂处理这种区域差异就可以了。下面是CheesePizza:
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
// 要制作披萨,需要工厂提供原料。
// 所以每个披萨类都需要从构造器参数中得到一个工厂,并把这个工厂存储在一个实例变量中。
public CheesePizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
@Override
void prepare() {
// prepare()方法一步一步地创建芝士披萨,每当需要原料时,就跟工厂要。
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
Pizza的代码利用相关的工厂生产原料。所生产的原料依赖所使用的工厂,Pizza类根本不关心这些原料,它只知道如何制作比萨。现在,Pizza和区域原料之间被解耦。
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
// 纽约店会用到纽约披萨原料工厂,由该原料工厂负责生产使用纽约风味披萨所需的原料
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (item.equals("cheese")) {
// 把工厂传递给每一个披萨,以便披萨能从工厂中取得原料
pizza = new CheesePizza(ingredientFactory);
} else if (item.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
} else if (item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
} else if (item.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
}
return pizza;
}
}
订单流程测试
- 首先我们需要一个纽约比萨店: PizzaStore nyStore = new NYPizzaStore();
- 现在已经有一个比萨店了,可以接受订单: Pizza pizza = nyStore.orderPizza("cheese");
- orderPizza()方法首先调用createPizza()方法: createPizza(type);
protected Pizza createPizza(String item) {
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
}
}
-
创建一个比萨的实例,然后将它和纽约原料工厂结合在一起 : new CheesePizza(ingredientFactory);
-
接下来需要准备比萨。一旦调用了prepare()方法,工厂将被要求准备原料 :
void prepare() System._out_.println("Preparing " + name); dough = ingredientFactory.createDough(); sauce = ingredientFactory.createSauce(); cheese = ingredientFactory.createCheese(); }
抽象工厂总结
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。让我们看看类图来了解其中的关系:
问:对于简单工厂和工厂方法之间的差异,我依然感到困惑。他们看起来很类似,差别在于,在工厂方法中,返回比萨的类是子类。能解释一下吗?
答:子类的确看起未很像简单工厂。简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。比方说,在工厂方法中,orderPizza()方法提供了一般的框架,以便创建萨,orderPizza()方法依赖工厂方法创建具体类,并制造出实际的比萨。可通过继承PizzaStore类,决定实际制造出的比萨是什么。// 简单工厂的做法,可以将对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。
问:工厂方法是不是潜伏在抽象工厂里面?
答:你的观察力很敏锐!是的,抽象工厂的方法经常以工厂方法的方式实现,这很有道理,对吧?抽象工厂的任务是定义一个负责创建一组产品的接口。这个接口内的每个方法都负责创建一个具体产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。所以,在抽象工厂中利用工厂方法实现生产方法是相当自然的做法。
抽象工厂与工厂方法的对比
- 抽象工厂和工厂方法都是负责创建对象。
- 抽象工厂是通过对象的组合。 工厂方法是继承。
- 定义了一个创建对象的接口,但由于子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到子类。所以利用工厂方法创建对象时,需要扩展一个类,并覆盖它的工厂方法。
- 整个工厂方法模式,只不过就是通过子类来创建对象,这种做法,客户只需要知道他们所使用的抽象类型就可以了,而由子类负责决定具体类型。将客户从具体类型中解耦。
- 抽象工厂是将一群相关的产品集合起来,用于创建相关或依赖对象的家族,而不需要明确指定具体类。要想使用这个工厂,必须先实例化它。
要点
- 所有的工厂都是用来封装对象的创建
- 简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类解耦。
- 工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象。
- 抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中。
- 所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合。
- 工厂方法允许类将实例化延迟到子类进行。
- 抽象工厂创建相关的对象家族,而不需要依赖它们的具体类。
- 依赖倒置原则,指导我们避免依赖具体类型,而要尽量依赖抽象。
- 工厂是很有威力的技巧,帮助我们针对抽象编程,而不要针对具体类编程。