本章将学习如何使用工厂模式来解放new操作符,如何从复杂的依赖关系中脱困。
场景描述
书中描述了一个订购披萨的场景,披萨有很多种,但是每一种的制作过程都一样。用户在披萨商店下单,商店根据种类来制作各种披萨。工厂模式的实现使得构造各种类型披萨变得灵活。工厂方法又细分成了三种,分别是:简单工厂、抽象工厂和工厂方法。下面就逐个来看下。
简单工厂
先学下简单工厂,看代码。
代码示例
定义披萨抽象类。为了简单,只给Pizza定义一个名称的成员变量,几个方法就是制作过程。在简化的场景下,每一种披萨的制作过是相同的,不同的是披萨的名称。
12345678910111213141516171819public abstract class Pizza {String name;public void prepare() {System.out.println("Preparing " + name);}public void bake() {System.out.println("Baking " + name);}public void cut() {System.out.println("Cutting " + name);}public void box() {System.out.println("Boxing " + name);}}定义披萨实现类,下面实现两种披萨。
1234567891011public class CheesePizza extends Pizza {public CheesePizza() {name = "Cheese Pizza";}}public class VeggiePizza extends Pizza {public VeggiePizza() {name = "Veggie Pizza";}}定义简单工厂。
12345678910111213public class SimplePizzaFactory {public Pizza createPizza(String type) {Pizza pizza = null;if (type.equals("cheese")) {pizza = new CheesePizza();} else if (type.equals("veggie")) {pizza = new VeggiePizza();}return pizza;}}定义一个披萨商店,用户是通过商店下单的。
12345678910111213141516public class PizzaStore {SimplePizzaFactory factory;public PizzaStore(SimplePizzaFactory factory) {this.factory = factory;}public Pizza orderPizza(String type) {Pizza pizza = factory.createPizza(type);pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;}}测试
123456789public class Test {public static void main(String[] args) {SimplePizzaFactory factory = new SimplePizzaFactory();PizzaStore store = new PizzaStore(factory);Pizza cheesePizza = store.orderPizza("cheese");System.out.println("-------------------------------");Pizza veggiePizza = store.orderPizza("veggie");}}
简单工厂确实挺简单的,只是把构造Pizza的代码放到一个统一的地方执行,这样的好处是只有这一个地方构造Pizza,以后实现改变时只需要修改工厂类即可。但是问题还是不支持扩展,对于新品Pizza无能为力,必须修改代码。
工厂方法
现在又有了新的需求,Pizza店要开加盟店,每家店都会有自己的风格,但是下单流程、以及制作流程要一致。所以上面只有一个工厂的简单工厂模式就不适合了,工厂方法模式把创建哪种披萨的实现直接交给了各个加盟店。下面来看代码。
代码示例
创建Pizza类
12345678910111213141516171819public abstract class Pizza {String name;public void prepare() {System.out.println("Preparing " + name);}public void bake() {System.out.println("Baking " + name);}public void cut() {System.out.println("Cutting " + name);}public void box() {System.out.println("Boxing " + name);}}创建一个纽约风味的芝士披萨
12345public class NYStyleCheesePizza extends Pizza {public NYStyleCheesePizza() {name = "NY Style Cheese Pizza";}}声明工厂方法,其实就是在PizzaStore变成一个抽象类,然后定一个抽象的创建Pizza的方法,所有加盟店都继承自PizzaStore,并且要实现创建Pizza的方法,这就是所谓的工厂方法。
12345678910111213public abstract class PizzaStore {public final Pizza orderPizza(String type) {Pizza pizza = creatrePizza(type);pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;}// 工厂方法protected abstract Pizza creatrePizza(String type);}创建纽约加盟店,纽约商店里构造的是纽约风味的披萨,为了简单,这里只生产一种纽约风味的芝士披萨。
123456789public class NYPizzaStore extends PizzaStore {protected Pizza createPizza(String type) {if (type.equals("cheese")) {return new NYStyleCheesePizza();}return null;}}测试
123456public class Test {public static void main(String[] args) {NYPizzaStore store = new NYPizzaStore();Pizza pizza = store.orderPizza("cheese");}}
综上,工厂方法模式定义了一个创建对象的接口,但是由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到了子类。这个模式下增加新品就不需要改代码,扩展就行了。
抽象工厂
学习抽象工厂之前,要添加一个小需求。需要在披萨里添加其他材料,比如酱料、蔬菜、芝士等。每个加盟店店口味不一样,使用的材料也不一样,要保证不能私自使用廉价材料,需要统一管理,下面看下抽象工厂如何来处理这个场景。为了演示简单,我们只在披萨中添加酱料和芝士。
代码示例
定义披萨抽象类。添加了sauce和cheese两个成员变量,分别用来保存披萨使用的是什么酱料和什么类型的那种奶酪。同时将prepare()变成抽象方法,由子类实现,在prepare时添加材料。
12345678910111213141516171819public abstract class Pizza {String name;String sauce;String cheese;public abstract void prepare();public void bake() {System.out.println("Baking " + name);}public void cut() {System.out.println("Cutting " + name);}public void box() {System.out.println("Boxing " + name);}}创建原料工厂,这个就是这里要讲的抽象工厂。材料抽象工厂类定义了两个方法,分别是创建酱料和奶酪。
1234public interface PizzaIngredientFactory {public String createSauce();public String createCheese();}创建一个酱料工厂
1234567891011public class NYPizzaIngredientFactory implements PizzaIngredientFactory {public String createSauce() {return "NY Sauce";}public String createCheese() {return "NY Cheese";}}创建一种披萨。创建的是芝士披萨,添加了一个原料工厂成员变量,用于在prepare()中获取原材料。这样Pizza类与原料就解耦了,可以复用各种原料工厂,而不必知道原料的细节。
123456789101112131415public class CheesePizza extends Pizza {PizzaIngredientFactory ingredientFactory;public CheesePizza(PizzaIngredientFactory ingredientFactory) {name = "NY Style Cheese Pizza";this.ingredientFactory = ingredientFactory;}public void prepare() {System.out.println("Preparing " + name);sauce = ingredientFactory.createSauce();cheese = ingredientFactory.createCheese();}}创建PizzaStore工厂方法,和工厂方法模式一样
12345678910111213public abstract class PizzaStore {public final Pizza orderPizza(String type) {Pizza pizza = createPizza(type);pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;}// 工厂方法protected abstract Pizza createPizza(String type);}创建纽约披萨商店
1234567891011public class NYPizzaStore extends PizzaStore {private PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();protected Pizza createPizza(String type) {if (type.equals("cheese")) {return new CheesePizza("New York Style Cheese Pizza", ingredientFactory);}return null;}}测试
123456public class Test {public static void main(String[] args) {PizzaStore store = new NYPizzaStore();store.orderPizza("cheese");}}
这里同时使用了工厂方法和抽象工厂。工厂方法用来创建PizzaStore,使用的方法是继承;抽象工厂用来创建各种原料,是通过对象组合的方式。
总结
三个工厂方法学下来可能会有点混乱,我们来理一下。第一种简单工厂最直观,就是把创建示例的代码放到一个单独的工厂类里去执行。另外两种使用场景会有点区别。工厂方法是在要实例化的父类上定一个创建对象抽象方法,所有的子类实现该方法,提供具体的实例化类的逻辑。我们把要实例化的对象分个层级的话,Pizza是一级,属于主类;而原材料是Pizza的成员变量,当做二级分类。那工厂方法处理的就是主类的实例化解耦。而抽象工厂,是需要创建产品族和想让制造的相关产品集合起来时使用的,在这个场景下就是创建一系列二级分类原材料。