一、文章来由
一天看完 hf设计模式 中的工厂模式,略微还有点消化不了,于是打算好好探究一下。其实不仅仅是这两个模式之间容易混淆,他们各自的定义也是挺绕的,下面我就仔细回过头翻书+查阅资料,重新捋一捋,研究一下这两个“工厂”。同时还是保持一个开放的心态,设计模式博大精深,很难一文以蔽之,于是《xx小览》系列文章又添新作。
Ps. 鉴于类容过长,我分成两部分来写,第一部分主要是自己对于看 hf设计模式 的总结,第二部分是来自书本和查找资料详细对比二者的不同~~
简而言之
简单工厂模式:工厂不可扩展,工厂收集了所有pizza制作的方法
工厂模式:工厂亦可扩展,与产品有对等的结构
抽象工厂:工厂有多个产品
二、简单工厂模式
这是最简单的一种工厂,所做的事情只有一个:
原来所有的 new 操作都在 PizzaStore 主类的 orderPizza 方法中,现在抽离出来,放到简单工厂中,且这个创建方法通常是静态的,见下图也有说。
所以,简单工厂说白了就是一个代理~~
目的也很简单,只是为了如果要添加一种 pizza 的时候不要修改主类就好(也就是所谓的对修改关闭)
PS. 代理,就是不在类中直接定义方法,而使用其它类的成员变量 调用方法(本例中是简单工厂代理了 createPizza() 方法)
PS2. 工厂方法跟模式没有什么联系,即使简单工厂里面也有工厂方法
严格来说,简单工厂方法都不能称作模式,反而更像一种编程习惯,但是经常用,而且总被人误认为一种模式,所以叫做模式了,其实是伪模式~~
详见代码:
以下源代码顺序:【PizzaStore.java】->【SimplePizzaFactory.java】->【Pizza.java】->【CheesePizza.java】->【main】
///PizzaStore.java
package headfirst.factory.pizzas;
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
///SimplePizzaFactory.java
package headfirst.factory.pizzas;
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
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;
}
}
///Pizza.java
package headfirst.factory.pizzas;
import java.util.ArrayList;
abstract public class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();
public String getName() {
return 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);
}
public String toString() {
// code to display pizza name and ingredients
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (int i = 0; i < toppings.size(); i++) {
display.append((String )toppings.get(i) + "\n");
}
return display.toString();
}
}
///CheesePizza.java
package headfirst.factory.pizzas;
public class CheesePizza extends Pizza {
public CheesePizza() {
name = "Cheese Pizza";
dough = "Regular Crust";
sauce = "Marinara Pizza Sauce";
toppings.add("Fresh Mozzarella");
toppings.add("Parmesan");
}
}
///PizzaTestDrive.java【main】
package headfirst.factory.pizzas;
public class PizzaTestDrive {
public static void main(String[] args) {
SimplePizzaFactory factory = new SimplePizzaFactory();
PizzaStore store = new PizzaStore(factory);
Pizza pizza = store.orderPizza("cheese");
System.out.println("We ordered a " + pizza.getName() + "\n");
pizza = store.orderPizza("veggie");
System.out.println("We ordered a " + pizza.getName() + "\n");
}
}
还是很清晰的~~
三、工厂模式
终于进入重头戏之一了,关于这个问题呢,hf设计模式 举了一个例子—加盟 pizza 店~~这样就有所谓的 New York Pizza、Chicago Pizza 和 California Pizza,如果用简单工厂,就必须有三个简单工厂来生产, New York Pizza工厂、Chicago Pizza工厂 和 California Pizza工厂,显然不可取。。。
工厂方法的核心:允许子类做决定
就是让 PizzaStore 的各个子类复制定义自己的createPizza() 方法。下面的PizzaStore是抽象类,createPizza() 方法也是抽象的~~
这样做神奇的地方在于,对于super class 的 PizzaStore , 它的方法 orderPizza() 调用了 Pizza 的准备、烘烤、切片、装盒一系列方法,但是 Pizza 却是抽象的,orderPizza() 并不知道是哪些实际的Pizza ,这就是所谓的解耦!
PizzaStore 也会调用它的抽象方法 createPizza() 取得Pizza对象,但是究竟会得到那种Pizza,对他来说也是透明的~~是“顾客”决定到哪个店买,才决定了是哪种 Pizza
上图中的 createPizza() 方法就是一个工厂方法,用来给不同的加盟店继承并去实现。createPizza() 方法的调用是在 orderPizza() 方法中的~~
接下来差不多该上代码了~~
注意,在下面的类中,工厂模式的 Pizza 主类以及 Pizza 的子类和简单工厂模式并没有什么不同~~
以下源代码顺序:【PizzaStore.java】->【NYPizzaStore.java】->【Pizza.java】->【CheesePizza.java】->【main】
///PizzaStore.java
package headfirst.factory.pizzafm;
public abstract class PizzaStore {
abstract Pizza createPizza(String item);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
///NYPizzaStore.java
package headfirst.factory.pizzafm;
public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}
///Pizza.java
package headfirst.factory.pizzafm;
import java.util.ArrayList;
public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();
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 < toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (int i = 0; i < toppings.size(); i++) {
display.append((String )toppings.get(i) + "\n");
}
return display.toString();
}
}
///NYStyleCheesePizza.java
package headfirst.factory.pizzafm;
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
///PizzaTestDrive.java【main】
package headfirst.factory.pizzafm;
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
}
}
整个过程是这样的:
工厂模式的创建者类和产品类是平行的类层级~~
另外,这里涉及一个很高逼的原则—依赖倒置原则
要依赖抽象,不要依赖具体类
再具体一点就是以下原则:
1、变量不开源持有具体类的引用。(如果使用new,就会持有具体类的引用,改用工厂方法可以避开这种做法)
2、不要让类派生自具体类。(因为派生自具体类,就会依赖具体类)
3、不要覆盖基类中已实现的方法。(如果基类已实现,就说明不是一个很适合继承的抽象类,基类中已实现的方法,应该由所有子类共享)
工厂模式 over
四、抽象工厂模式
这个是重中之重了~~
对于抽象工厂模式,hf设计模式 又想了另一个很好的例子,但是这次不只是 NY 或是 Chicago 简单的地域变换(仅仅是地区问题,就可以只使用工厂模式解决),这个例子是关于原料要是统一的工厂发配,但是每个地区的口味不同,原料也不一样~~
注意:以下箭头标注的,每一个地方的原料是固定的,如果这些 sauce 都一样
所以关键在于:
这些 Pizza 都是采用相同的组件制造(有酱料、干酪、蛤蜊。。。),但每个区域对于这些组件有不同的制造(也就是说有 NY 原料工厂、Chicago 原料工厂。。。)
感悟:重点在于,是一组抽象的制造,然后子类继承并实现这一组的new
而每个具体的原料工厂就生产当地必须的原料 即可。
其实说穿了上面这一点,感觉抽象工厂也就差不多了~~
于是。。。。。。。。。。。是时候上源码了~~
特别注意:这个例子里面抽象工厂的 Pizza 类改变比较大,因为 Pizza 的原料是各个地方生产的,所以 Pizza 主类的 prepare() 方法是抽象的,而对于各种 Pizza 子类,实现 prepare() 方法时,也只是简单的使用了下面这句~~
sause = ingredientFactory.createSauce();
Pizza 类根本不关心这些原料,它只关心如何制作。所以 Pizza 和区域原料之间解耦,于是不会出现工厂模式中的类似 NYStyleCheesePizza 的类了。
以下源代码顺序:【PizzaStore.java】->【NYPizzaStore.java】->【PizzaIngredientFactory.java】->【NYPizzaIngredientFactory.java】->【Pizza.java】->【CheesePizza.java】->【Cheese.java】->【ReggianoCheese.java】->【main】
///PizzaStore.java(PizzaStore 没什么变化)
package headfirst.factory.pizzaaf;
public abstract class PizzaStore {
protected abstract Pizza createPizza(String item);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
///NYPizzaStore.java
package headfirst.factory.pizzaaf;
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
///各地的 PizzaStore 从原料工厂进货,实例化 NY 原料工厂
PizzaIngredientFactory ingredientFactory =
new NYPizzaIngredientFactory();
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if (item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if (item.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}
///PizzaIngredientFactory.java【原料工厂抽象类】
package headfirst.factory.pizzaaf;
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
///NYPizzaIngredientFactory.java
package headfirst.factory.pizzaaf;
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
public Clams createClam() {
return new FreshClams();
}
}
///Pizza.java【Pizza 抽象类,注意 prepare() 方法是抽象的】
package headfirst.factory.pizzaaf;
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;
abstract void prepare();
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
void setName(String name) {
this.name = name;
}
String getName() {
return name;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("---- " + name + " ----\n");
if (dough != null) {
result.append(dough);
result.append("\n");
}
if (sauce != null) {
result.append(sauce);
result.append("\n");
}
if (cheese != null) {
result.append(cheese);
result.append("\n");
}
if (veggies != null) {
for (int i = 0; i < veggies.length; i++) {
result.append(veggies[i]);
if (i < veggies.length-1) {
result.append(", ");
}
}
result.append("\n");
}
if (clam != null) {
result.append(clam);
result.append("\n");
}
if (pepperoni != null) {
result.append(pepperoni);
result.append("\n");
}
return result.toString();
}
}
///CheesePizza.java
package headfirst.factory.pizzaaf;
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
///Cheese.java
package headfirst.factory.pizzaaf;
public interface Cheese {
public String toString();
}
///ReggianoCheese.java
package headfirst.factory.pizzaaf;
public class ReggianoCheese implements Cheese {
public String toString() {
return "Reggiano Cheese";
}
}
///PizzaTestDrive.java【main】
package headfirst.factory.pizzaaf;
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza + "\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza + "\n");
pizza = nyStore.orderPizza("clam");
System.out.println("Ethan ordered a " + pizza + "\n");
pizza = chicagoStore.orderPizza("clam");
System.out.println("Joel ordered a " + pizza + "\n");
}
}
代码over,那么整个流程是什么样的呢?
我觉得整个结构,我画了一张图,比书中的图更清晰一些(纯用Windows自带画图工具,画了好久T^T。。。)
下面是 hf 设计模式书中的图片~~
这样,整个流程就拉下来了~~但是还来手打一次,重要的事情不说3遍,也来个2遍,哈哈
1、首先要新建一个 nyPizzaStore,然后用该 store order 一个Pizza,假设为 cheese
2、然后 orderPizza() 是主类就实现的方法, nyPizzaStore 继承了,orderPizza() 方法里面先 createPizza。
3、createPizza() 主类是抽象的,在 nyPizzaStore 实现了,nyPizzaStore 类的 createPizza() 方法先建立一个原料工厂,很明显这个工厂是 NYPizzaIngredientFactory 类,但任向上赋值给 PizzaIngredientFactory 类。
4、然后 createPizza() 会去根据 type 判断是什么类型的 Pizza,如果 order 的是 cheese,就 new 一个 CheesePizza 对象,同时要把刚刚创建的 NYPizzaIngredientFactory 作为参数传入,为 Pizza 提供原料(被 Pizza 的 prepare() 方法使用),于是变成了:
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory); //创建 Pizza 对象
pizza.setName("New York Style Cheese Pizza");
}
5、在具体的 Pizza 对象中,调用 prepare() 方法(Pizza 对象,自带 原料工厂 对象)。该 prepare() 方法在 Pizza 主类中是抽象方法,在具体子类中实现,如下:
void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough(); //向工厂索要原料
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
6、最后 Pizza 在 createPizza() 方法中准备好了,return 一个 Pizza 对象,会继续回到 PizzaStore 主类,执行orderPizza() 方法,执行
pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza;
也就是说,从orderPizza 进入,最后又在orderPizza 结束,函数栈的调用过程就是这样的,“首尾呼应”~~
关于抽象工厂定义,有一副简单易懂的图:
这里的client 就是具体的区域商店,比如是 NYPizzaStore:
好啦,小览(一)差不多结束了~~期待小览(二)
—END—