为什么要使用工厂?
当看到new时,就会想到具体这个词。
是的,当使用new时,确实是在实例化一个具体累,所以用的确实是实现,而不是接口。针对接口编程,可以隔离掉以后系统可能发生的一大堆改变。因为如果代码是针对接口编写的,那么通过多态,可以使用任何接口的实现类。
除了使用new操作符之外,还有更多制造对象的方法。实例化这个活动不应该总是公开地进行。
使用一个披萨店的例子
假设有一个披萨店,需要一个披萨管理系统。
我们决定使用面向对象的方式来实现这个系统,经过分析后,代码可能会这么写:
/**
* 这里一个披萨店的实现类。
* */
public class PizzaStore {
/**
* 订购披萨的方法,返回一个披萨饼对象。
* */
public Pizza orderPizza() {
// 创建一个披萨饼对象。
Pizza pizza = new Pizza("披萨饼");
return pizza;
}
}
做法是定义一个Pizza类,有一个name字段,重写toString()方法。
接着编写一个PizzaStore类,该类有一个orderPizza()方法,用于订购披萨。
最后编写main函数进行测试。
需要更多的披萨类型
经过一段时间后,披萨店有了更多种类的披萨饼,简单的一个Pizza对象已经应付不来了。我们需要更多的披萨对象,为了让系统有弹性,我们把Pizza类定义为抽象的,让具体的披萨对象集成它。
/**
* 披萨对象的抽象类,所有类型的披萨对象继承自该类。
* */
public abstract class Pizza {
public Pizza() { }
public Pizza(String name) {
this.name = name;
}
/**
* 对披萨进行前期处理。
* 擀面、加佐料等工艺。
* */
public void prepare() {
System.out.println(" ==> 对" + name + "进行前期处理...");
}
/**
* 烘培披萨。
* */
public void bake() {
System.out.println(" ==> 对" + name + "进行烘培处理...");
}
/**
* 切割披萨。
* */
public void cut() {
System.out.println(" ==> 对" + name + "进行切割处理...");
}
/**
* 包装处理。
* */
public void box() {
System.out.println(" ==> 对" + name + "进行包装处理...");
}
@Override
public String toString() {
return "披萨 [名称=" + name + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String name;
}
/**
* 芝士披萨对象,继承自披萨抽象类。
* */
public class CheesePizza extends Pizza {
public CheesePizza() {
super.setName("芝士披萨");
}
}
/**
* 希腊披萨,继承自披萨抽象类。
* */
public class GreekPizza extends Pizza {
public GreekPizza() {
super.setName("希腊披萨");
}
}
/**
* 意大利香肠披萨,继承自披萨抽象类。
* */
public class PepperoniPizza extends Pizza {
public PepperoniPizza() {
super.setName("意大利香肠披萨");
}
}
我们有了具体的披萨类型,接着重写orderPizza方法,通过类型来决定创建具体的披萨:
/**
* 这里一个披萨店的实现类。
* */
public class PizzaStore {
/**
* 订购披萨的方法,返回一个披萨饼对象。
* */
public Pizza orderPizza(String type) {
Pizza pizza;
// 通过类型来确定返回哪种披萨。
if("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
} else if ("pepperoni".equals(type)) {
pizza = new PepperoniPizza();
} else {
return null;
}
// 擀面上料、烘培、切片、装盒。
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
进行测试:
通过披萨类型订购了两种不同的披萨,非常方便,但是……
压力来自于增加更多的披萨类型
经过了一段时间,有出现了披萨新种类,比如海鲜蛤哩披萨和素食披萨等等,披萨店也学会了这些披萨的做法并要把它们加入到系统中。同时,希腊风味披萨卖得不太好,要将它从系统中移除。
很明显,如果实例化“某些”具体类,每次都要去修改PizzaStore类源码,因为要修改orderPizza()方法中的语句。
还有一点,这段创建披萨的代码放在了PizzaStore中的ordarPizza () 方法,与pizza的处理方法写到了一起。如果有一天披萨店的菜单对象也需要创建披萨,则需要拷贝orderPizza()方法中创建披萨的那段if-else代码到菜单对象。修改时就要修改两处,如果有20个地方用到了创建披萨的这段代码呢?
是时候将创建披萨的代码封装起来了!
我们需要将if-else的代码搬到另一个对象中,这个新对象只管如何创建披萨。如果任何对象想要创建披萨,找它就对了。我们称这个新对象为“工厂”(factory)。创建这个工厂,并修改现有代码:
首先修改PizzaStore类中的orderPizza()方法,将new披萨的那段if-else代码从方法中移除:
接下来创建新对象,披萨工厂:
/**
* SimplePizzaFactory是披萨的工厂类,它只做一件事,就是创建披萨。
*/
public class SimplePizzaFactory {
/**
* 通过类型来确定返回哪种披萨。
* */
public static Pizza createPizza(String type) {
Pizza pizza;
// 通过类型来确定返回哪种披萨。
if ("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("pepperoni".equals(type)) {
pizza = new PepperoniPizza();
} else if ("clam".equals(type)) {
pizza = new ClamPizza();
} else if ("veggie".equals(type)) {
pizza = new VeggiePizza();
} else {
return null;
}
return pizza;
}
}
最后,在PizzaStore类的orderPizza()方法中,通过工厂来创建披萨:
/**
* 这里一个披萨店的实现类。
* */
public class PizzaStore {
/**
* 订购披萨的方法,返回一个披萨饼对象。
* */
public Pizza orderPizza(String type) {
// 通过披萨工厂创建披萨。
Pizza pizza = SimplePizzaFactory.createPizza(type);
// 擀面上料、烘培、切片、装盒。
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
测试结果如下:
定义简单工厂
简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。但由于经常被使用,所以也归纳到了工厂方法中。不要因为简单工厂不是一个“真正的”模式,就忽略了它的用法。让我们来看看现在披萨管理系统的类图:
PizzaStore是工厂的“客户”。PizzaStore现在通过SimplePizzaFactory取得披萨实例。
SimplePiazzaFactory是工厂方法,用来创建披萨。
Pizza是“产品”的抽象类,每个具体产品必须拓展抽象的Pizza类,并设计称一个具体类。这样一来,就可以被工厂创建,并返回给客户。
到这里简单工厂的介绍就差不多了,多谢它为我们暖身。接下来登场的是两个重量级的模式,它们都是工厂。