《Head First Design Patterns》:第4章,工厂(Factory)模式

场景

披萨店

简单工厂

场景描述

披萨店提供预定功能,可以提供多种类型(参数type)披萨的预定;已知的是:a)披萨店会时常调整所提供的披萨种类,b)披萨制作过程(包含:准备、烘焙、切片、包装)不会经常变化;代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PizzaStore {
Pizza orderPizza(String type) {
Pizza pizza;

if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}
}

采用这样的设计,每当披萨店有新品披萨上架、或下架某类披萨时就需要对该代码进行修改,如下图所示:

解决方案

使用简单工厂,对orderPizza(String type)方法中变化的部分进行封装,使orderPizza(String type)方法对“修改关闭”,于是我们将创建对象的代码从orderPizza(String type)方法中抽象出来,如下;这样orderPizza(String type)方法只需要从工厂中得到比萨对象,然后对它进行一系列的制作过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

}

简单工厂的定义

工厂方法

工厂方法的定义

场景描述

现在考虑开展披萨加盟店,经营者为保障加盟店营运的质量,希望这些加盟店尽量使用之前披萨店的代码(即,PizzaStore);又已知的是,每个地区的加盟店可能想要提供不同风味的比萨。按照简单工厂的方式,一个可能的设计可能会是:每家加盟店都有自己的工厂来创建具体的披萨对象,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class NYPizzaFactory {
public Pizza createPizza(String type) {
//
}
}

public class ChicagoPizzaFactory {
public Pizza createPizza(String type) {
//
}
}

public class CaliforniaPizzaFactory {
public Pizza createPizza(String type) {
//
}
}

但是调研中发现,并不是所有的加盟者都希望完全按照PizzaStore的流程来制作披萨,往往他们都是一些有着丰富经验的厨师,他们想要加入自己的一些过程设计。这样的话,PizzaStore就不能满足需要了,因为它把制作披萨的过程牢牢地绑定到了其内部。

解决方案

我们a)把PizzaStore类声明为抽象类,b)把简单工厂中的方法拿回到PizzaStore类中,并将其声明为抽象方法,c)为每个区域的店创建一个PizzaStore类的子类。这样,可以让比萨的制作活动仍然局限在PizzaStore类,而同时又让这些加盟店能够自由地制作该区域风味的披萨。我们称createPizza()为工厂方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;

pizza = createPizza(type);

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}

abstract Pizza createPizza(String type);
}

纽约州的披萨店:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}

如此一来,每个地区的加盟店类型都继承PizzaStore类,并各自决定如何制作自己的披萨(即,定义各自的createPizza()方法);并且每个PizzaStore的具体子类都有自己的比萨变体。这样就做到了所有加盟店仍然使用PizzaStore框架,并可以使用已经非常不错的订单系统(即,orderPizza()方法)。

抽象工厂

抽象工厂模式的定义

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

每个具体工厂都可以生产一整组产品。

场景描述

为了确保加盟店使用高质量的原料,我们打算建造原料工厂,并将原料运送到各家加盟店;并且,针对不同区域的加盟店,原料工厂能提供只适合该区域加盟店的原料。

已知所有披萨所需要的原料种类都是相同的(面团、酱料、芝士、海鲜佐料),这些原料的制作方式根据区域的不同而有差异。

解决方案

工厂接口PizzaIngredientFactory中定义所有原料创建的方法:

1
2
3
4
5
6
7
8
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}

为每个区域建造一个工厂,使用该区域所特有的一组原料供工厂使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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() {
// code to print pizza here
}
}

奶酪披萨使用工厂接口PizzaIngredientFactory来准备原料:

1
2
3
4
5
6
7
8
9
10
11
12
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();
}
}

具体的披萨店知道从哪个工厂进行原料采购:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
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;
}
}

工厂方法VS抽象工厂

工厂方法 抽象工厂
1.不需要另外定义接口,只需要定义抽象方法;
2.使用继承,把对象的创建委托给子类
1.抽象工厂的任务是定义一个负责创建一组产品的接口,接口中的每个方法都负责创建一个具体产品;
2.使用对象组合,对象的创建被实现在工厂接口所暴露出来的方法中;

参考

  1. https://wickedlysmart.com/head-first-design-patterns/