0%

设计模式学习之-04工厂模式

tips:

本文的代码均在代码连接

1. 遇到的问题

前面三章我们一直在强调对接口编程,以增加程序的灵活性和降低耦合性,但是我们仍然不断地使用new关键字,例如下面这段代码

1
Duck duck = new MallardDuck();

我们想使用接口来保持代码的灵活性,但是我们不得不创建一个具体类的实例。

当我们大量相关的类时,我们通常会将代码写成下面的样子

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 我们有大量的Duck的子类,直到运行时我们才能知道哪个类是需要实例化的
*/
Duck duck;

if (picnic) {
duck = new MallardDuck();
} else if (hunting) {
duck = new DecoyDuck();
} else if (inBathTub) {
duck = new RubberDuck();
}

这里我们有一系列的类的实例,决定运行时哪个进行实例化是由一系列的判断条件来决定的。

当有条件增加或减少时,我们就需要打开这段代码进行修改,这实际上违背了“对修改关闭”的设计原则。为了扩展新的类型,必须对其修改。

2. 以披萨店为例

假设你是一家披萨店的老板,你的店里有三种披萨,点单的流程包括,1. 选择披萨类型 2. 准备材料 3. 烘焙 4. 切分 5. 装盒。那么点单的代码可能会写成下面的样子:

代码路径

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

现在你开发出了新产品,Clam和Veggie,并且删掉了一个老产品Greek,所以你就需要将代码修改成下面的样子

代码路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}

// 不变的部分
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}

披萨的类型不断变化我们就需要不断地修改if–else if的判断条件。在上面的代码中我用注释标出了变化的部分和不变的部分。我们想要把变化的部分独立出去。

3. 简单工厂

3.1. 封装创建对象的代码

我们想要把对象创建的部分拿到orderPizza()函数外面。但是,怎么做呢?我们将要做的是把创建对象的代码移到另一个只关心创建披萨的对象中。

这个新的对象我们称之为—工厂。工厂负责对象创建的细节。一旦我们有了SimplePizzaFactory,我们的orderPizza方法就成了对象的一个client。orderPizza需要一个披萨时就要求工厂制作一个。

3.2. 创建一个SimplePizzaFactory

代码路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimplePizzaFactory {

public Pizza createPizza(String type) {
Pizza pizza = null;

if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} 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;
}
}

3.2.1. 一个问题

  1. 这不就是把代码换了个类吗?这么写有什么提升呢?

    SimplePizzaFactory可能会有很多clients。我们仅仅看到了orderPizza这个方法,可能还会有一个PizzaShopMenu方法使用工厂来获取披萨的描述和价格,可能还会有一个HomeDelivery类,同样也是工厂的client,它用不同于现在处理披萨的方式来处理。

    因此通过把创建披萨的方法封装到另一个类中,我们现在只需要修改一个地方即可完成修改。

3.3. 重写PizzaStore类

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;
}

Pizza orderPizza(String type) {
Pizza pizza;

pizza = factory.createPizza(type);

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

return pizza;
}

}

3.4. 简单工厂

简单工厂并不是一个真正意义上的设计模式,它更像是一个编程习惯。虽然不是一个真正的设计模式,但是它也经常被使用。

下面是PizzaStore的类图。

测试代码:代码路径

1
2
3
4
5
6
7
public static void main(String[] args) {
SimplePizzaFactory sp = new SimplePizzaFactory();
PizzaStore ps = new PizzaStore(sp);

ps.orderPizza("cheese");
ps.orderPizza("clam");
}

执行结果

1
2
3
4
5
6
7
8
cheese + milk + flour
220 degree 45 minutes
cut
box
clam + milk + flour
250 degree 40 minutes
cut
box

4. 工厂方法

4.1. 分店

你的披萨店开的非常好,其他人也想在他们的城市开设披萨分店。你想让他们使用经过验证的代码。但是问题也随之而来,分店可能需要根据当地的饮食习惯制作不同种类的披萨。

鉴于我们上面的简单工厂方法是有效的,我们仍然可以采用相同的思路:用不同的工厂来解决这个问题,例如三个不同的工厂—NYPizzaFactory, ChicagoPizzaFactory, CaliforniaPizzaFactory。

代码像下面这样:

1
2
3
4
5
6
7
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.orderPizza("Veggie");

ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
nyStore.orderPizza("Veggie");

4.2. 另一种方式—工厂方法

4.2.1. 实现

我们真正想要的是一个框架,这个框架把店铺和披萨制作绑定起来,与此同时,保证灵活性。

为了控制这些流程,一个方法是把披萨制作的活动固定到PizzaStore中。我们把createPizza()方法放到PizzaStore类中,但是这次是作为一个抽象函数。

下面我们重写PizzaStore类:代码路径

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

pizza = createPizza(type);

if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}

return pizza;
}

abstract Pizza createPizza(String type);
}

在这个设计中,我们让抽象函数abstract Pizza createPizza(String type);来负责各种不同种类披萨的制作,因此我们会创建多个PizzaStoreAbstr的子类,来实现多种种类的披萨的制作

通过对PizzaStore类的改写,我们的处理逻辑从原来的由一个对象(simpleFactory)来负责具体类(Pizza)的实例化,变成现在的由一组子类来负责。

实例化披萨的职责被交给了abstract Pizza createPizza(String type);,createPizza函数的身份就是工厂。这个方法我们称为工厂方法。

  1. 工厂方法是一个抽象方法,因此子类可以决定对象的创建
  2. 工厂方法返回一个对象
  3. 超类里的其他成员不需要直到工厂方法的具体实现细节。

类图:

实现NYPizzaStore:代码路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class NYPizzaStore extends PizzaStoreAbstr{
@Override
Pizza createPizza(String type) {
Pizza pizza = null;

if (type.equals("cheese")) {
pizza = new NYStyleCheesePizza();
} else if (type.equals("greek")) {
pizza = new NYStyleGreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new NYStylePepperoniPizza();
} else if (type.equals("clam")) {
pizza = new NYStyleClamPizza();
} else if (type.equals("veggie")) {
pizza = new NYStyleVeggiePizza();
}

return pizza;
}
}

实现具体的披萨:代码路径

1
2
3
4
5
6
7
8
9
10
11
class NYStyleCheesePizza extends  Pizza {
@Override
public void prepare() {
System.out.println("NY cheese + milk + flour");
}

@Override
public void bake() {
System.out.println("NY 220 degree 45 minutes");
}
}

测试代码:代码路径

1
2
3
4
5
6
7
8
public static void main(String[] args) {
PizzaStoreAbstr nyStore = new NYPizzaStore();
PizzaStoreAbstr chicagoStore = new ChicagoPizzaStore();

nyStore.orderPizza("cheese");

chicagoStore.orderPizza("cheese");
}

执行结果

1
2
3
4
5
6
7
8
NY cheese + milk + flour
NY 220 degree 45 minutes
cut
box
Chicago cheese + milk + flour
Chicago 220 degree 45 minutes
cut
box

4.2.2. 定义

The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

把上面的类图抽象一下,得到下面的类图:

  1. 所有的产品都实现同一个Product接口,保证所有使用产品的类都与接口相关而和具体的产品实现无关。
  2. Creator是一个类,除工厂方法外,实现了所有与产品相关的方法。
  3. 工厂方法是一个抽象函数
  4. Creator的子类负责实现工厂方法,决定具体Product的创建

4.2.3. 依赖倒置原则

Depend upon abstractions. Do not depend upon concrete classes。

依赖抽象而非具体类。

设计方针:

  1. 变量不可以拥有具体类的引用

    如果使用new,就会持有具体类的引用。

  2. 不要让类派生自具体类

    从一个抽象派生,例如接口或抽象类

  3. 不要重写基类中实现的方法

    基类中实现的方法应该是子类都相同的方法

5. 抽象工厂

回到披萨店的问题上,我们设计的这个框架具有弹性且满足设计原则。但是我们想要确保每一家分店都能使用高质量的原材料,因此我们需要建立工厂提供原材料。并且每个地区材料在做法上可能会有些不同。

5.1. 我们需要做什么

  1. 实现工厂类的接口
  2. 为每个地区建立工厂
  3. 实现一组原材料类,如ReggianoCheese,RedPepper等,这些类可以在地区间共享

代码路径

工厂接口

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
27
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类

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
31
32
33
34
35
36
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() {
//...
}
}

原材料准备放在prepare函数中进行

具体的Pizza

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

重写PizzaStore

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
31
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;
}
}

测试代码

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

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
--- Making a New York Style Cheese Pizza ---
Preparing New York Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a ---- New York Style Cheese Pizza ----
Thin Crust Dough
Marinara Sauce
Reggiano Cheese


--- Making a Chicago Style Cheese Pizza ---
Preparing Chicago Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Joel ordered a ---- Chicago Style Cheese Pizza ----
ThickCrust style extra thick crust dough
Tomato sauce with plum tomatoes
Shredded Mozzarella

5.2. 定义

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

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

实际上抽象工厂里的方法就是一个工厂方法。