工厂模式是一种常见的设计模式了。当我们实例化一个具体类时往往会第一时间想到用new。当子类需要被分成不同的情况去实例化时,我们一般想到的就是写一些if判断,而这种处理方式会给我们今后代码的维护带来很多麻烦。如何将实例化具体类的代码从应用中抽离,或者封装起来,使它们不会干扰应用的其他部分?
首先我们从披萨店的设计中看看工厂模式为什么需要出现?
假设你有一个披萨店,原来你要生产一个披萨代码你可能是这样写:
Pizza orderPizza(){
// 创建pizza
Pizza pizza = new Pizza();
// 对披萨的加工
...
return pizza;
}
但是,当你需要更多口味的披萨时,你可能会考虑将代码改写一下:
Pizza orderPizza(String type){
// 创建pizza
Pizza pizza;
if ("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
}
// 对披萨的加工
...
return pizza;
}
随着生意的发展,问题又来了。披萨的种类需求越来越多,需要新增或删除披萨的种类时往往需要去修改上面的代码。
让我们回到OO设计原则,看看怎么改动这段代码比较好,最基本的单一原则。将创建披萨的部分抽离出来,看成一个新对象,这个新对象只管如何创建对象。我们称这个对象为“工厂”。
简单工厂方法
简单工厂方法与其说是工厂模式的一种,更不如说它其实就是我们的一种编程习惯。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if ("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
}
return pizza;
}
}
这样,我们的披萨店在生产披萨的时候代码就改写成这样了
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza = factory.createPizza(type);
return pizza;
}
public static void main(String args[]){
PizzaStore pizzaStore = new PizzaStore(new SimplePizzaFactory());
Pizza pizza = pizzaStore.orderPizza("cheese");
System.out.println(pizza.toString());
}
}
简单工厂方法仅仅是遵守了单一原则,将创建对象的责任承担了下来,如果有新增披萨种类我们还是只能在createPizza方法中加判断条件,这还是违背了开闭原则(对扩展开放,对修改关闭)的。有没有解决办法?答案是有的,工厂方法模式出现了。
工厂方法模式
工厂类定义成了接口,而每新增的披萨类型,就增加该披萨类型对应工厂类的实现,这样工厂的设计就可以扩展了,而不必去修改原来的代码。工厂方法模式通过让子类决定该创建的对象是什么,把类的实例化推迟到子类,来达到将对象创建的过程封装的目的。根据这个思想,我们开两家专门卖纽约口味的和芝加哥口味披萨的分店,让他们分别为我们总店提供想要的披萨吧。
先把总店变成抽象类
public abstract class PizzaStore {
abstract Pizza createPizza(String type);
public Pizza orderPizza(String type){
Pizza pizza = createPizza(type);
return pizza;
}
}
然后我们来开两家分店
芝加哥分店
public class ChicagoPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
if ("cheese".equals(type)) {
return new ChicagoStyleCheesePizza();
} else{
return null;
}
}
}
纽约分店
public class NYPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
if ("cheese".equals(type)) {
return new NYStyleCheesePizza();
} else{
return null;
}
}
}
披萨也要分口味
public class ChicagoStyleCheesePizza extends CheesePizza {
@Override
public String toString() {
return super.toString()+" of Chicago";
}
}
public class NYStyleCheesePizza extends CheesePizza {
@Override
public String toString() {
return super.toString()+" of NY";
}
}
点个芝士披萨看看吧
public class PizzaTest {
public static void main(String args[]){
PizzaStore nyStore = new NYPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println(pizza.toString());
PizzaStore chicagoStore = new ChicagoPizzaStore();
pizza = chicagoStore.orderPizza("cheese");
System.out.println(pizza.toString());
}
}
执行结果截图如下:
抽象工厂模式
工厂方法模式能够封装具体类型的实例化,相当于说搭了一个框架,让子类决定要如何实现,说白了也是一些简单工厂方法的集合。我们在用的时候只需要关心是哪个店出来的披萨就好,不需要关心具体实现。但是还有一个我们不想看到的问题,当披萨的不同口味的分店越来越多时,同样会多很多新的类。披萨店依赖于所有的披萨对象,因为它直接创建这些披萨对象。对具体类的依赖过多这恰恰不遵守了OO设计原则的依赖倒置原则(要依赖抽象,不要依赖具体类)。依赖倒置原则,很抽象,暂时不用理会。披萨店原本依赖于很多具体的披萨对象,披萨的口味不一样也就是做法、原料有略微不同而已,现在这些不同口味的披萨我们都可以抽象成披萨,也就是说披萨店依赖于抽象出来的披萨(Pizza),至于具体的披萨对象,它们则依赖抽象出来的披萨(因为它们都实现了Pizza接口),这样一来依赖就倒置了。我们可以将我们的工厂拆分成四个角色:抽象的工厂和具体的工厂;抽象的产品和具体的产品。
下面看具体的一个例子吧
抽象出一个披萨类
public abstract class Pizza {
String name; // 名称
String material; // 材料
String practice; // 做法
abstract void prepare();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "[name:"+name+";material:"+material
+";practice:"+practice+"]";
}
}
抽象出一个工厂接口,负责做披萨的材料和做法
public interface PizzaFactory {
public String getMaterial();
public String getPractice();
}
纽约风味披萨的实现
public class NYPizzaFactory implements PizzaFactory {
@Override
public String getMaterial() {
return "纽约的面粉";
}
@Override
public String getPractice() {
return "纽约的做法";
}
}
芝士披萨
public class CheesePizza extends Pizza {
PizzaFactory factory;
public CheesePizza(PizzaFactory factory){
this.factory = factory;
}
void prepare() {
System.out.println(name+"准备中...");
material = factory.getMaterial();
practice = factory.getPractice();
}
}
负责做纽约披萨的分店,总店的定义还是没变
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaFactory factory = new NYPizzaFactory();
if ("cheese".equals(type)) {
pizza = new CheesePizza(factory);
pizza.setName("纽约口味的芝士披萨");
}
return pizza;
}
}
准备待续,买个披萨吧
public class PizzaTest {
public static void main(String args[]){
PizzaStore nyStore = new NYPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
pizza.prepare();
System.out.println(pizza.toString());
}
}
执行结果如图所示:
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
无论是简单工厂方法,工厂方法模式,还是抽象工厂模式,它们的作用都是为了解耦,在平时工作的使用中根据需求灵活地使用,而不必拘束于只使用一种。
以上就是我对《Head First 设计模式》中关于工厂模式的一些理解及参考书上的例子自己写的一些例子。记录一下方便今后复习。详细代码见Github