设计模式笔记04-工厂模式
1 引言
除了使用new操作符之外,还有更多制造对象的方法。你将了解到实例化这个活动不应该总是公开地进行,也会认识到初始化经常造成耦合问题。你不希望这样,对吧?读下去,你将了解工厂模式如何从复杂的依赖中帮你脱困。
2 正文
2.1 new有什么不对劲
当有一群相关的具体类时,通常会写出这样的代码:
Duck duck;
if (picnic)
{
duck = new MallardDuck();
}
else if (hunting)
{
duck = new DecoyDuck();
}
else if (inBathTub)
{
duck = new RubberDuck();
}
这里有一些要实例化的具体类,究竟实例化哪个类,要在运行时由一些条件决定。 当看到这样的代码,一旦有变化或扩展,就必须重新打开这段代码进行检查和修改。通常这样修改过的代码将造成部分系统更难维护和更新,而且更容易犯错。
但是,总是要创建对象吧!而Java只提供一个new关键词创建对象,不是吗?还能有些什么? 在技术上,new没有错,毕竟这是Java的基础部分。真正的犯人是我们的老朋友“change",以及它是如何影响new的使用的。 针对接口 编程,可以隔离掉以后系统可能发生的一大堆改变。为什么呢?如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体类时,等于是自找麻烦,因为一旦加入新的具体类,就必须改变代码。也就是说,你的代码并非”对修改关闭“。想用新的具体类型来扩展代码,必须重新打开它。 所以,该怎么办?当遇到这样的问题时,就应该回到OO设计原则去寻找线索。别忘了,我们的第一个原则用来处理改变,并帮助我们”找出会变化的方面,把它们从不变的部分分离出来“。
2.2 识别变化的部分
假设你又一个披萨店,身为对乡村内最先进的披萨主任,你的代码可能这么写
public Pizza orderPizza(String type)
{
Pizza pizza;
//根据顾客的口味匹配不同种类的披萨
//问题出现了,随着时间的变化,披萨的菜单会改变,这里就必须已改再改
if (type.equals("cheese"))
{
pizza = new CheesePizza();
}
else if
{
}
//这个是我们不想改变的地方。因为披萨的准备、烘烤、切片、包装,多年来都保持不变,
//所以这部分代码不会改变,只有发生这些动作的披萨会改变
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
很明显,如果实例化”某些“具体类,将使orderPizza()出问题,而且也无法让orderPizza()对修改关闭;但是,现在我们已经知道哪些会改变,哪些不会改变,该是试用封装的时候了。
2.3 建立一个简单披萨工厂
首先,把创建对象的代码从orderPizza()中抽离,然后把这部分的代码搬到另一个对象中,这个新对象只管如何创建披萨。如果任何对象想要创建披萨,找它就对了。
public class SimplePizzaFactory {
//SimplePizzaFactory只做一件事情,帮它的客户创建Pizza
//首先,在这个工厂内定义一个createPizza()方法。所有客户用这个方法来实例化新对象
public Pizza createPizza(String type) {
Pizza pizza = null;
//这个代码没有什么变动,和原本orderPizza()方法中的代码一样,依然是以pizza的类型为参数
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;
}
}
除此之外,利用静态方法定义一个简单的工厂,这是很常见的技巧,常被称为静态工厂。为何使用静态方法?因为不需要使用创建对象的方法来实例化对象。但请记住,这也有缺点,不能通过继承来改变创建方法的行为。
重做PizzaStore类
public class PizzaStore {
//为PizzaStore加上一个对SimplePizzaFactory的引用
SimplePizzaFactory factory;
//PizzaStore的构造器,需要一个工厂作为参数
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
//而orderPizza()方法通过简单传入订单类型来使用工厂创建pizza
//请注意,我们把new操作符替换成工厂对象的创建方法,这里不再使用具体实例化!
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
2.4 定义简单工厂
简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。有些开发人员的确是把这个编程习惯误认为是”工厂模式“(Factory Patter)。 不要因为简单工厂不是一个”真正的“设计模式,就忽略了他的用法。谢谢简单工厂来为我们暖身。接下来登场的是两个重量级的模式,他们都是工厂。
2.5 工厂方法模式
对象村Pizza店经营又称,现在大家都希望对象村Pizza点能够在自家附近有加盟店。 其中一件加盟店希望工厂能制造纽约风味的Pizza:薄饼、美味的酱料和少量的芝士;另一家加盟店希望能制造芝加哥风味的Pizza:厚饼、重味的酱料和大量的芝士。 如果利用SimplePizzaFactory,写出三种不用的工厂,那么各地的加盟店都有自己适合工厂可以使用,这是一种做法。 在 推广SimpleFactory时,你发现加盟店的确是你采用你的工厂创建Pizza,但是其他部分,却开始采用他们自创的流程:烘烤的做法有些差异、不要切片、使用其他厂商的盒子。 再想想这个问题,你真的希望能够建立一个框架,把加盟店和创建pizza绑在一起的同时又保持一定的弹性。
工厂方法类的设计
public abstract class PizzaStore {
/*
* 工厂方法用来处理对象的创建,并将这样的行为封装在子类中。
* 这样,客户程序中关于超类的代码就和子类对象创建代码