在之前的章节中,我们提到了要针对接口编程,所以在DuckDemo中,我们利用了接口的多态的特性,可以动态的改变对象。但是这样也是存在一定的问题的,下面我们就来分析这个问题。
一个pizza引发的血案
假设我们开了一个pizza店,我们提供了一个程序能够让顾客自动下单订购pizza,那么我们的代码可以如下实现:
首先我们定义一个pizza类
1 | package com.changyuan.pizzaDemo; |
然后定义一个pizzaStore的类:
1 | package com.changyuan.pizzaDemo; |
我们看这个类里面的createPizza的方法,我们根据不同的参数new不同的类的实例,NYStyleCheesePizza等这六个类都是继承pizza类的具体的子类。这里我们利用接口的多态思想,可以动态的调用具体的方法。
但是当我们需要添加新的类型的pizza时怎么办呢?比如我们需要添加Miami的cheese口味的pizza,那么就必须要修改createPizza的方法,在里面继续添加if。这就违背了我们的一个重要的设计原则
对修改关闭,对扩展开放
因此我们需要引入新的设计模式来解决这个问题,这就是工厂模式。
上面的代码归档位置
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/pizzaDemo
我们看上面的代码存在什么问题?
就一个问题,我们在针对具体的实现再编程,也就是我们在具体的new每一个类的实例。我们接触到的第一个原则是:
找出变化的部分
而这里面变化的部分就是createPizza方法。这里我们引入工厂(工厂处理创建对象的细节),来统一处理这个变化的部分。
我们定义一个工厂类来处理对象的创建
1 | public class SimplePizzaFactory { |
然后就可以改造PizzaStore了。
1 | public class PizzaStore { |
这样我们就把需要修改的部分单独提出来了,放到了工厂类中了。
那么这样有什么好处呢?好像没什么啊,就是单独的将变化的部分放到了一个类中。实际上这只是一点小改变,当我们需要添加新的加盟店铺时,就只需要使用SimplePizzaFactory创建就好了,尤其是当需要修改种类时,只需要修改SimplePizzaFactory就好了,不用修改两个类。
类图如下所示

代码归档如下
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/pizzaSimpleFactoryDemo
当我们真的有了两个加盟店后,我们可以直接调用SimplePizzaFactory去创建对象,但是有个问题哎,就是每个加盟店修改自己的内容,比如说pizza的种类等等,它只能修改SimplePizzaFactory类,这必然会影响到别的加盟店,那么我们就需要方式去解决这个问题。
在上面的代码中,我们每次创建一个实例时,都是用的同一个pizzaStore
1 | SimplePizzaFactory factory = new SimplePizzaFactory(); |
我们这里要做的就是对于每个加盟店,创建其自己的store类(继承自pizzaStore),然后在子类中自己创建createPizza方法,让子类自己决定其创建的对象。
pizzaStore类:1
2
3
4
5
6
7
8
9
10
11
12public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
abstract Pizza createPizza(String type);
}
每个店铺有自己的createPizza方法:
1 | public class NYPizzaStore extends PizzaStore { |
1 | public class ChicagoPizzaStore extends PizzaStore { |
代码归档如下
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/pizzaFactory
工厂模式
工厂模式就是定义了一个创建对象的接口,但是由子类来决定要实例话的类是哪一个,工厂方法让类把实例化推迟到子类中。
工厂模式一般有两个类,一个是创建者类,一个是产品类。
我们回到我们最初的代码,假设我们要在其中加入一种California风味的pizza,那么就需要修改PizzaStore类。实际上这个版本的pizzaStore依赖于所有的pizza对象,因为在这个函数里,直接创建了这些pizza对象。而且每新增一个pizza种类,就会让pizzaStore多一个依赖。

而我们通过工厂模式,我们的pizzaStore依赖于抽象的pizza类,而具体的pizza类也依赖于抽象的pizza类(因为它们实现了pizza的接口)。高层组件和底层组件都依赖于抽象的pizza类,这就是依赖倒置原则。

第五个设计原则:
依赖倒置原则:要依赖抽象,不要依赖具体的类
看看我们当前的实现,对于不同的pizza类型,它们的原料都是在类中写死的。换句话说,它们都是依赖于各自的pizza类的。那么如果我们要将其统一管理起来要怎样处理呢?
假设我们有两个pizzaStore的店铺,它们分别使用相同的原料家族,但是具体的每个原料是不同的,那么我们首先需要创建基础的原料接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public interface Dough {
public String toString();
}
public interface Clam {
public String toString();
}
public interface Cheese {
public String toString();
}
public interface Pepperoni {
public String toString();
}
public interface Veggies {
public String toString();
}
然后我们可以定义具体的原料类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
31public class ThinCrustDough implements Dough {
public String toString() {
return "Thin Crust Dough";
}
}
public class ThickCrustDough implements Dough {
public String toString() {
return "Thick Crust Dough";
}
}
public class Spinach implements Veggies {
@Override
public String toString() {
return "Spinach";
}
}
public class SlicedPepperoni implements Pepperoni {
public String toString() {
return "Sliced Pepperoni";
}
}
public class ReggianoCheese implements Cheese {
public String toString() {
return "Reggiano Cheese";
}
}
public class RedPepper implements Veggies {
public String toString() {
return "RedPepper";
}
}
等等等等的具体的原料类型。
然后我们可以创建我们的pizzaStore
1 | public class NYPizzaIngredientFactory implements PizzaIngredientFactory { |
我们可以看到不同的原料工厂会使用不同的原料类型。
然后我们来看我们的pizzaStore,每个pizzaStore都会调用自己的PizzaIngredientFactory来生产所需要的原料,然后将原料作为输入送到各中pizza制作的函数中,这样就形成了口味不同的pizza。虽然调用的是同一个CheesePizza接口,但是由于原料的不同,进儿产生了不同的pizza。
1 | public class ChicagoPizzaStore extends PizzaStore { |
而我们的各种pizza子类,也继承自pizza类,在子类中实现各自的prepare方法,这样我们在测试用例里面就可以直接调用orderPizza方法来实现不同的pizza类型的prepare函数调用了。
代码归档路径
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/pizzaAbstractFactory
抽象工厂模式
抽象工厂模式提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确的指定类。
上面的原料模型代码就是抽象工厂的应用。
比较一下工厂方法和抽象工厂方法
在工厂方法中,我们创建了一个工厂类,来创建所需要的pizza类型
1 | public class ChicagoPizzaStore extends PizzaStore { |
而PizzaStore中,也是实际上调用的工厂类的createPizza方法
1 | public abstract class PizzaStore { |
而在抽象工厂中,我们看我们实际上提供了一个接口,利用接口的特性,创建了产品的家族
1 | public interface PizzaIngredientFactory { |
而当我们具体创建各个原料类的时候,实际上new的是各个子类
1 | PizzaIngredientFactory ingredientFactory = new ChicagoPizzaIngredientFactory(); |
而在使用时则直接调用各个子类中的方法。
1 | public class CheesePizza extends Pizza { |
1 | public class ChicagoPizzaStore extends PizzaStore { |
一句话就是一个是类,一个是接口(定义了一族类),工厂方法用的是继承,而抽象工厂方法用的是组合。
两种模式的类图如下所示:

