4 Factory Pattern(工厂模式)
前言:工厂模式是为了解决new的问题
案例分析:
REQ1:Vander作为pizza店的老板,具有一整套制作pizza的流程,准备食材、烘焙、切片、包装,随着pizza种类的渐渐增加,设计如下:
public class PizzaStore {
public Pizza 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();
} else if(type.equals("pepperoni")) {
pizza = new Pepperoni();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
分析:随着pizza店的发展,后期发现要制作越来越多的pizza,而且种类也是超级多,多达几十种pizza,而且有些旧类型的pizza,几乎没人点,greek和pepperoni pizza就从菜单上撤离了,而增加了FruitsPizza、HawaiiPizza、DurianPizza,所以上面orderPizza()方法就会频繁地改动,经常改动导致pizza店的订单系统经常处在维护状态,这非常不利于接单,很明显,实例化某些具体类,将使得orderPizza()方法出问题,而且这违反了“开放-关闭原则”,这个方法就无法对修改关闭了;但是现在已经知道哪些是会改变的,哪些是不会改变的部分,接下来用封装来解决上面的问题。
解决方法1:Vander 又开始改进设计了
将创建pizza的代码封装起来转移到另一个对象中,然后orderPizza()方法专门负责pizza的制作工作。new pizza这件事专门交给其他类去负责。
重新修改PizzaStore的代码:
public class PizzaStore {
public SimplePizzaFactory simplePizzaFactory;
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
this.simplePizzaFactory = simplePizzaFactory;
}
public Pizza orderPizza(String type) {
Pizza pizza = simplePizzaFactory.producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
简单工厂的代码:
public class SimplePizzaFactory {
public Pizza producePizza(String type) {
Pizza pizza = new Pizza();
if(type.equals("cheese")) {
pizza = new CheesePizza();
} else if(type.equals("greek")) {
pizza = new GreekPizza();
} else if(type.equals("pepperoni")) {
pizza = new Pepperoni();
}
return pizza;
}
}
现在相当于,每次修改SimplePizzaFactory的代码的时候,可以暂时不对线上的版本进行影响,修改完SimplePizzaFactory之后再替换掉线上的SimplePizzaFactory就OK了。而orderPizza()方法相当于变成了SimplePizzaFactory的使用者,它使用pizza工厂来完成pizza的new操作。
下面定义简单工厂,实际上简单工厂不算做一个设计模式,更多地是一种编程习惯,我们会经常用到它,所以也是非常重要的。
REQ2:Vander的Pizza店名气非常大,吸引了很多大腕来加盟,但是由于区域的差异,每个加盟店都可能需要作出不同风格的pizza,例如四川的希望水果Pizza里面有辣椒,广东的则希望水果Pizza里面多一点菠萝,这受到了开店地区当地人口味的影响。
首先要完成加盟店,所以PizzaStore的SimpleFactory就成了一些地区PizzaFactory,如GuangDongPizzaFactory、SiChuanPizzaFactory。然后他们各自使用各自的Pizza类,例如广东的创造广东类型的Pizza-GDPizza;四川的创作自己类型的Pizza-SCPizza,然后他们各自再覆盖原来的pizza里面的prepare、bake、cut、box等方法,他们自创了自己的流程,而且box的时候还用了别的商家的盒子,这完全就是打着Vander Pizza店的牌子在乱搞嘛。
问题来了,怎样才能加强质量把控,又不失弹性,让这些地区Pizza店可以依照自己的口味去。
解决方法2:工厂方法模式
Vander思前想后就是想不到合适的办法,这时候他只能去请教Panda大师,Panda大师说这个可以用工厂方法解决,首先要给加盟Pizza点使用框架,先从PizzaStore开始改造:
由于想控制加盟店制作Pizza类中的bake、cut、box,不能让这些加盟店随便乱弄,也不能让它们使用别的公司的包装,需要把控整个制作过程,把PizzaStore的producePizza方法设为抽象类,让GDPizzaStore和SCPizzaStore去继承,它们在生产完Pizza之后只能调用父类的orderPizza方法,继续完成准备、烘焙、切片、包装。让我们来看看现在的改造吧:
public abstract class PizzaStore {
public final Pizza orderPizza(String type) {
Pizza pizza = producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza producePizza(String type);
}
public abstract class Pizza {
protected String name;
//面团类型
protected String dough;
//酱料
protected String sauce;
//佐料
protected ArrayList toppings = new ArrayList(10);
public void prepare() {
System.out.println("Preparing " + name);
System.out.println(" Tossing dough ...");
System.out.println(" Adding sauce ...");
System.out.println(" Adding toppings: ");
for(int i=0; i
public class GDStyleFruitsPizza extends Pizza {
public GDStyleFruitsPizza() {
name = "GuangDong Style Fruits Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("GuangDong Delicious Cheese");
toppings.add("GuangDong Pipeapple");
}
}
public class SCStyleFruitsPizza extends Pizza {
public SCStyleFruitsPizza() {
name = "SiChuan Style Fruits Pizza";
dough = "Thick Crust Dough";
sauce = "Marinara Sauce";
toppings.add("SiChuan Delicious Cheese");
toppings.add("SiChuan Pepper");
}
}
public class GDPizzaStore extends PizzaStore {
@Override
public Pizza producePizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new GDStyleCheesePizza();
} else if(type.equals("fruits")) {
pizza = new GDStyleFruitsPizza();
} else if(type.equals("durian")) {
pizza = new GDStyleDurianPizza();
} else if(type.equals("Hawaii")) {
pizza = new GDStyleHawaiiPizza();
}
return pizza;
}
}
public class SCPizzaStore extends PizzaStore {
@Override
public Pizza producePizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new SCStyleCheesePizza();
} else if(type.equals("fruits")) {
pizza = new SCStyleFruitsPizza();
} else if(type.equals("durian")) {
pizza = new SCStyleDurianPizza();
} else if(type.equals("Hawaii")) {
pizza = new SCStyleHawaiiPizza();
}
return pizza;
}
}
说明:工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样客户程序中关于超类的代码就和子类对象创建代码解耦了。也就是说不管以后有多少加盟店,父类PizzaStore都不会去改变了,任由子类PizzaStore天翻地覆,PizzaStore都以不变应万变。工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
Vander就纳闷了,我之前用的简单工厂难道不能实现吗,然后Vander就想Panda大师这么干到底有什么优势,接着Vander发现他也能给像Panda那样做到把控整个控制过程。实际上关键的代码就是将PizzaStore的orderPizza()方法写成final,让子类加盟店都无法修改整个制作过程,接着让Pizza类的box()方法也写成final,这样子子类Pizza就无法使用别的商家的包装了。接着Vander给出了他的实现。
以下是主要的代码:
public class GDPizzaStore extends PizzaStore {
public SimplePizzaFactory simplePizzaFactory;
public GDPizzaStore(SimplePizzaFactory simplePizzaFactory) {
super(simplePizzaFactory);
}
}
public class GDPizzaFactory extends SimplePizzaFactory{
public Pizza producePizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new GDStyleCheesePizza();
} else if(type.equals("fruits")) {
pizza = new GDStyleFruitsPizza();
} else if(type.equals("durian")) {
pizza = new GDStyleDurianPizza();
} else if(type.equals("Hawaii")) {
pizza = new GDStyleHawaiiPizza();
}
return pizza;
}
}
public class PizzaStore {
public SimplePizzaFactory simplePizzaFactory;
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
this.simplePizzaFactory = simplePizzaFactory;
}
public final Pizza orderPizza(String type) {
Pizza pizza = simplePizzaFactory.producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
public abstract class SimplePizzaFactory {
public abstract Pizza producePizza(String type);
}
说明:从代码上看来,其实确实跟工厂方法模式差不多,只是把producePizza方法放到了简单工厂而已,但是这么操作最后在运行的时候,必须要实例化相应的工厂,例如GDPizzaStore需要传入GDPizzaFactory,则需要实例化GDPizzaFactory,并且还不能传错,传错了就会导致最后广东的Pizza店卖的确是其它工厂造出来的Pizza。
下面比对一下工厂方法模式和简单工厂是怎么造出Pizza的。
简单工厂的制作Pizza方法:
public class Main {
public static void main(String[] args) {
GDPizzaFactory gdFactory = new GDPizzaFactory();
GDPizzaStore gdPizzaStore = new GDPizzaStore(gdFactory);
gdPizzaStore.orderPizza("fruits");
SCPizzaFactory scFactory = new SCPizzaFactory();
SCPizzaStore scPizzaStore = new SCPizzaStore(scFactory);
scPizzaStore.orderPizza("fruits");
}
}
工厂方法的制作Pizza方法:
public class Main {
public static void main(String[] args) {
GDPizzaStore gdPizzaStore = new GDPizzaStore();
Pizza gdFruitsPizza1 = gdPizzaStore.orderPizza("fruits");
SCPizzaStore scPizzaStore = new SCPizzaStore();
Pizza scfruitsPizza2 = scPizzaStore.orderPizza("fruits");
}
}
最后Vander发现原来他的方法需要先实例化GDPizzaFactory,然后将Factory作为参数放进GDPizzaStore里面,Vander就想,既然都是GDPizzaFactory对应GDPizzaStore,SCPizzaFactory对应SCPizzaStore,那我直接在GDPizzaStore里面实例化GDPizzaFactory,SCPizzaStore里面实例化SCPizzaFactory不就好了。接着又进行了一轮改造:
public class GDPizzaStore extends PizzaStore {
public GDPizzaStore() {
simplePizzaFactory = new GDPizzaFactory();
}
}
public class PizzaStore {
protected SimplePizzaFactory simplePizzaFactory;
public final Pizza orderPizza(String type) {
Pizza pizza = simplePizzaFactory.producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
说明:最后虽然在运行时的调用是一样的,但是这里相当于直接让GDPizzaStore和GDPizzaFactory直接绑定在一起了,也增加了代码的耦合度。而且每次多开一个加盟店,都需要实现PizzaStore和PizzaFactory多一次,确实也没多大必要。
REQ3:由于Pizza大家已经吃腻了,所以Vander为了招来更多的客户,想扩展菜单了,现在不只是卖Pizza了,现在Pizza店学习必胜客的方式,还卖鸡翅、汉堡并且还出售各种独家饮料。那么怎么扩展接口呢?在PizzaStore基类中继续添加produceFiredChicken()、produceBurger()、produceBeverage(),这么做的后果就是所有的加盟店都得跟着做改变,但是有些加盟店Pizza生意做得火热,觉得根本没必要添加这么多种类进去。这时候Vander想到了他之前实现的SimpleFactory2,就是将简单工厂编程“抽象工厂”,让子类工厂去完成产品的创建,这样做的好处就是加盟店的PizzaStore都可以不用动,而且把这些食物的创建都放到了一个大工厂去做这么一件事情了。
下面是Vander的设计:
这个设计图看起来有一定的复杂度,但是其实就是在SimpleFactory2上的PizzaFactory改为了FoodFactory添加了几个生产其他产品的方法,本来只有一个Pizza的抽象类,现在多了Beverage、Burger、FiredChicken这几个抽象类,相当于现在FoodFactory是一个大工厂来生产各种各样的产品。可能你会发现这FoodFactory里面实际上不就是工厂方法模式嘛,没错!就是这样相当于FoodFactory里面生产各种各样的食物,但是生产的食物是什么样的Pizza、什么样的炸鸡,都是由子类做决定的。
实际上SimpleFactory2已经不算是简单工厂了,简单工厂应该是更单纯一点的,通过一个含参的方法来实例化产品类。SimpleFactory2经过改进之后已经成为了抽象工厂模式了。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么,这样一来,客户就从具体的产品中被解耦了。上面这段话通俗地说,就是像子类工厂完成Pizza的创建,然后PizzaStore在orderPizza()方法中直接是拿基类Pizza类来进行prepare、bake、cut、box等操作,而不用理会是哪种口味哪个区域的Pizza。从而达到了PizzaStore与具体的Pizza类的解耦。
下面我们梳理一下整个过程,首先刚开始PizzaStore依赖于具体的Pizza类,如下图所示:
而且随着Pizza类型的增加,依赖将会越来越多,代码里面减少具体的类的依赖是必须要做的,接下来我们看看我们后面的实现:
这样就依赖抽象的类Pizza。(遵循了依赖抽象而不是具体的类的原则)
我们发现高层组件(PiizaStore)和底层组件(具体的Piizza子类)都依赖于Pizza的抽象类。我们一般进行设计的时候,首先设计PizzaStore,PizzaStore要能生产出很多具体的不同类型的Pizza,其实这时候你可以倒过来想,从具体的Pizza想起,发现应该先抽象出一个Pizza基类,然后这个Pizza基类直接给PizzaStore用,然后就发现这个抽象的Pizza基类就像桥梁一样连接着PizzaStore和具体的Pizza类。这里面就包含了“依赖倒置原则”,即在从上往下的设计的同时,不妨先想想能不能从下往上。
下面有三个指导方针来避免违反“依赖倒置原则”:
1、变量不可以持有具体类的引用。(如果需要用new,用工厂代替)
2、不要让类派生自具体类。(如果派生具体类了,你就会依赖具体类,请派生一个抽象)
3、不要覆盖基类中已实现的方法(基类已经实现了,说明具有通用性,否则请设为抽象)
最后的最后,我们又来总结我们现在现有的设计模式武器。
面向对象基础
抽象、封装、多态、继承
六大原则
设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程。
设计原则三:多用组合,少用继承。
设计原则四:为交互对象之间的松耦合设计而努力。
设计原则五:对扩展开放,对修改关闭。
设计原则六:依赖抽象,不要依赖于具体的类 。
模式
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式:提供一个接口,用于创建相关对象或者是依赖对象的家族,而不需要明确指定具体的类(说白了就是一系列同类型的工厂方法集合)。