【HF设计模式】04-工厂模式
声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。
摘要
《Head First设计模式》第4章笔记:结合示例应用和代码,介绍工厂相关模式(简单工厂、工厂方法、抽象工厂),具体内容包括遇到的问题、问题的解决方案、方案遵循的 OO 原则、以及达到的效果。
目录
- 摘要
- 1 简单工厂
- 1.1 示例应用:对象村披萨店
- 1.2 对应新需求:调整披萨品类
- 1.3 简单工厂定义
- 2 工厂方法
- 2.1 对应新需求:连锁加盟店
- 2.1.1 问题1:不同地区具有不同特色
- 2.1.2 问题2:部分加盟店流程不规范
- 2.2 工厂方法定义
- 2.3 示例代码
- 2.3.1 Java 示例
- 2.3.2 C++11 示例
- 3 OO 原则:依赖倒置
- 3.1 原则的应用
- 3.2 原则遵循指南
- 4 抽象工厂
- 4.1 对应新需求:原料工厂
- 4.2 抽象工厂定义
- 4.3 示例代码
- 4.3.1 Java 示例
- 4.3.2 C++11 示例
- 5 工厂方法 vs 抽象工厂
- 5.1 模式对比
- 5.2 应用对比
- 6 设计工具箱
- 6.1 OO 基础
- 6.2 OO 原则
- 6.3 OO 模式
- 参考
1 简单工厂
1.1 示例应用:对象村披萨店
示例应用是对象村披萨店(Objectville Pizza Store)的业务系统,包括菜单展示、订购披萨、递送披萨等功能。
订购披萨的代码如下:
Pizza orderPizza(String type) {
Pizza pizza;
if (type.equals("奶酪")) { pizza = new 奶酪Pizza(); }
else if (type.equals("希腊")) { pizza = new 希腊Pizza(); }
else if (type.equals("香肠")) { pizza = new 香肠Pizza(); }
pizza.prepare(); // 前期准备:擀揉面团、添加酱料、添加顶层配料
pizza.bake(); // 烘烤
pizza.cut(); // 切片
pizza.box(); // 装盒
return pizza;
}
思考题:
上述 orderPizza() 方法的实现,违反了下面哪些设计原则?(多选)【参考答案在第 20 行】
A. 分离变与不变(Identify the aspects of your application that vary and separate them from what stays the same.)
B. 针对接口编程(Program to interfaces, not implementations.)
C. 优先使用组合(Favor composition over inheritance.)
D. 松耦合设计(Strive for loosely coupled designs between objects that interact.)
E. 开闭原则(Classes should be open for extension, but closed for modification.)
参考答案:A E D
A 分离变与不变:
1. 代码第 4-6 行,是变化的地方,随着时间推移,披萨品类会发生改变。
As the pizza selection changes over time, you’ll have to modify this code over and over.
2. 代码第 8-11 行,是不变的地方,披萨的准备、烘烤、切片、包装流程,多年来一直保持不变。
For the most part, preparing, cooking, and packaging a pizza has remained the same for years and years.
E 开闭原则:
这份代码没有对修改关闭,每当披萨店调整所供应的披萨时,我们就得打开这份代码来修改。
This code is NOT closed for modification. If the Pizza Store changes its pizza offerings, we have to open this code and modify it.
D 松耦合设计:
因为工厂模式让用户与具体产品解耦,所以 D 应该也可以选。
1.2 对应新需求:调整披萨品类
最新的市场调研显示,竞争对手的蛤蜊披萨和素食披萨大受欢迎。为保持竞争力,对象村披萨店也将推出这两款披萨。同时,希腊披萨销量明显下滑,需要将其从菜单中移除。
为了让 orderPizza()
“对修改关闭”,我们需要将“创建披萨的代码”(变化的方面)提取出来,并进行封装。
制造对象,并非只有使用
new
操作符。
There is more to making objects than just using the new operator.
实例化这项活动,不应该总是公开进行,因为这经常会导致耦合问题。
Instantiation is an activity that shouldn’t always be done in public and can often lead to coupling problems.
下面是封装后的代码,这个只关注如何创建披萨的类叫做:简单工厂(Simple Factory)。
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("奶酪")) { pizza = new 奶酪Pizza(); }
else if (type.equals("香肠")) { pizza = new 香肠Pizza(); }
else if (type.equals("蛤蜊")) { pizza = new 蛤蜊Pizza(); }
else if (type.equals("素食")) { pizza = new 素食Pizza(); }
return pizza;
}
}
使用 SimplePizzaFactory
的 orderPizza()
如下:
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;
}
}
【质疑】这么做有什么好处?看起来只是把创建披萨的代码从一个地方移到了另一个地方而已。
【回答】将创建对象的代码与使用对象的代码分离,会带来很多好处:
- 首先,除了
orderPizza()
,SimplePizzaFactory
还有很多其它用户,例如披萨菜单pizzaMenu()
会通过工厂获取披萨的价格和描述;披萨递送homeDelivery()
也会通过工厂获取披萨的相关信息。
把创建披萨的代码封装在工厂类中,一方面,可以实现代码复用;另一方面,在需要调整披萨的创建时,可以只修改工厂类,便于代码维护。 - 另外,对于用户端代码(如
orderPizza()
),通过分离变化、对修改关闭,提高了代码的稳定性。 - 还有,通过封装对象的创建,返回“接口类型”对象,可以确保用户“针对接口编程”,从而提高代码的灵活性和可扩展性。
1.3 简单工厂定义
简单工厂实际上并不是一个设计模式;它更多是一种编程的习惯用法。
The Simple Factory isn’t actually a Design Pattern; it’s more of a programming idiom.
简单工厂定义一个工厂类,可以根据不同的参数,创建不同类型的对象,返回这些对象共同的超类型。
简单工厂不是一个“真正的”模式,不意味着它不值得我们了解。来看看披萨店的新类图:
PizzaStore
是工厂的 用户;- 组合
SimplePizzaFactory
类型对象factory
,用于创建Pizza
类型对象;
注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallCreatePizzaOfFactory
- 组合
SimplePizzaFactory
是创建Pizza
的 工厂;- 定义
createPizza()
方法,经常被声明为静态类型,应该是应用中唯一引用具体Pizza
类的地方。
- 定义
Pizza
是工厂创建的 抽象产品,定义为抽象类;- 为产品定义统一的接口(这里的“接口”是广义的“接口概念”)
- 为部分方法提供一般性的实现,可以被子类覆盖。
奶酪Pizza
、香肠Pizza
、蛤蜊Pizza
、素食Pizza
是 具体产品 类;- 继承抽象产品类
Pizza
,实现必要的方法。
- 继承抽象产品类
2 工厂方法
2.1 对应新需求:连锁加盟店
对象村披萨店生意很好,已经成功塑造“对象村披萨”品牌形象,并展开连锁经营,很多商家希望加盟。
但是面临两个问题:
- 加盟店位于不同的地区,而不同地区的披萨有着不同的特色,包括纽约的薄皮披萨、芝加哥的深盘披萨,以及加州的薄脆披萨等等;
- 部分加盟店的流程不够规范,例如有时会忘记切片、使用第三方包装盒等等。
对于上述问题,我们来逐一分析下,首先是问题1。
2.1.1 问题1:不同地区具有不同特色
如果所有披萨都由简单工厂 SimplePizzaFactory
来创建,那么代码如下:
public class SimplePizzaFactory {
public Pizza createPizza(String region, String type) {
Pizza pizza = null;
if (region.equals("纽约")) {
if (type.equals("奶酪")) { pizza = new 纽约特色奶酪Pizza(); }
else if (type.equals("香肠")) { pizza = new 纽约特色香肠Pizza(); }
else if (type.equals("蛤蜊")) { pizza = new 纽约特色蛤蜊Pizza(); }
else if (type.equals("素食")) { pizza = new 纽约特色素食Pizza(); }
}
else if (region.equals("芝加哥")) {
if (type.equals("奶酪")) { pizza = new 芝加哥特色奶酪Pizza(); }
else if (type.equals("香肠")) { pizza = new 芝加哥特色香肠Pizza(); }
else if (type.equals("蛤蜊")) { pizza = new 芝加哥特色蛤蜊Pizza(); }
else if (type.equals("素食")) { pizza = new 芝加哥特色素食Pizza(); }
}
return pizza;
}
}
思考一下:
- 当增加新的地区特色时,例如"加州特色",代码需要怎样修改?
- 在调整某个地区特色的披萨品类时,例如为"纽约特色"增加“希腊披萨”,代码需要怎样修改?
很明显,上述变更都需要修改 SimplePizzaFactory
类,代码没有对修改关闭。(看起来,简单工厂不适合创建容易发生变更的多组对象)
为此,我们可以
- 遵循“封装变化”原则,对每个地区特色进行封装;
- 遵循“针对接口编程”原则,为所有地区特色定义统一的接口。
2.1.1.1 披萨工厂类图
于是,我们拥有了多家披萨工厂:
2.1.1.2 披萨工厂的定义
其中 纽约特色PizzaFactory
的定义如下:
public class 纽约特色PizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("奶酪")) { pizza = new 纽约特色奶酪Pizza(); }
else if (type.equals("香肠")) { pizza = new 纽约特色香肠Pizza(); }
else if (type.equals("蛤蜊")) { pizza = new 纽约特色蛤蜊Pizza(); }
else if (type.equals("素食")) { pizza = new 纽约特色素食Pizza(); }
return pizza;
}
}
2.1.1.3 披萨店的定义
新的 orderPizza()
实现如下:
唯一的变化是,将实例变量 factory
的类型由具体类 SimplePizzaFactory
调整为抽象类 PizzaFactory
。
public class PizzaStore {
PizzaFactory factory; // 定义披萨工厂抽象类型实例变量
// 通过传入不同工厂类对象的引用(如 纽约特色PizzaFactory、芝加哥特色PizzaFactory),构造不同地区特色的披萨店
public PizzaStore(PizzaFactory 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;
}
}
2.1.1.4 订购披萨
订购 纽约特色奶酪Pizza
的方式如下:
PizzaFactory factory = new 纽约特色PizzaFactory();
PizzaStore store = new PizzaStore(factory);
Pizza pizza = store.orderPizza("奶酪");
2.1.1.5 订购流程
完整的订购流程如下:
PizzaFactory factory = new 纽约特色PizzaFactory();
PizzaStore store = new PizzaStore(factory);
this.factory = factory;
Pizza pizza = store.orderPizza("奶酪");
Pizza pizza = factory.createPizza("奶酪");
// 由工厂类创建具体披萨return new 纽约特色奶酪Pizza();
pizza.prepare();
- 准备:面团、酱料、奶酪
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
2.1.1.6 系统类图
将 PizzaFactory
、Pizza
和 PizzaStore
放在一起,系统类图如下:
现在可以通过不同的工厂来创建不同地区特色的披萨,问题1已经解决,我们再来分析问题2。
2.1.2 问题2:部分加盟店流程不规范
对象村披萨店系统的流程久经考验,能够提供高品质的服务,所以可以要求所有加盟店统一使用对象村的流程。
但是,在保证服务质量的基础上,也要给加盟店一定的创新空间,以满足个性需求。
采用问题1的解决方案,可以支持不同地区特色,可以保证流程统一,但是却很难实现个性化行为。
所以还需要寻找新的解决方案。不过,问题1的方案是否体现了某种设计模式的思想呢?这个问题,我们暂时放一放,先聚焦眼前问题。
为了解决上述问题,我们决定采用继承的方式,将 PizzaStore
定义为抽象类,作为一个框架(framework),加盟店在框架的基础上,定义具有地区特色的具体类。
框架是一组相互协作的类,它们构成“一类特定软件”的可复用设计。
A framework is a set of cooperating classes that make up a reusable design for a specific class of software.
当使用框架时,我们复用应用的主体,编写主体调用的代码。
When you use a framework, you reuse the main body of the application and write the code it calls.
2.1.2.1 披萨店类图
PizzaStore
抽象类:- 声明
createPizza()
抽象方法,用于创建具有不同地区特色的披萨;
因为这是一个用于“创建对象”的方法,所以将其称为 工厂方法。 - 定义
orderPizza()
方法,并声明为final
,禁止被子类覆盖;
可以确保所有子类行为的一致性。 - 定义
pizzaMenu()
方法,提供默认实现;
子类可以根据需要进行覆盖,以实现创新行为。
- 声明
地区特色PizzaStore
具体类:- 实现
createPizza()
方法,创建具有地区特色的披萨; - 还可以定义其它创新行为。
- 实现
这样,新的设计可以解决上述所有问题:支持不同地区特色、在关键流程上保持一致、在其它流程上保留创新空间。
2.1.2.2 披萨店的定义
下面是 PizzaStore
框架的定义。
和使用工厂类比较:在创建披萨时,直接调用子类实现的 createPizza()
,而不是通过工厂对象来调用其方法 factory.createPizza()
。
public abstract class PizzaStore {
// 定义抽象的工厂方法,将创建披萨的责任交给具体类
public abstract Pizza createPizza(String type);
public final Pizza orderPizza(String type) {
Pizza pizza;
// 通过子类实现的工厂方法来创建披萨
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
public void pizzaMenu() {
// 略
}
}
2.1.2.3 加盟店的定义
下面是 纽约特色PizzaStore
的定义。
和使用工厂类比较:createPizza()
方法由子类来实现,而不是由工厂类 纽约特色PizzaFactory
来实现。
public class 纽约特色PizzaStore extends PizzaStore {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("奶酪")) { pizza = new 纽约特色奶酪Pizza(); }
else if (type.equals("香肠")) { pizza = new 纽约特色香肠Pizza(); }
else if (type.equals("蛤蜊")) { pizza = new 纽约特色蛤蜊Pizza(); }
else if (type.equals("素食")) { pizza = new 纽约特色素食Pizza(); }
return pizza;
}
}
2.1.2.4 订购披萨
下面是 纽约特色奶酪Pizza
的订购方式。
和使用工厂类比较:用户在构造 PizzaStore
时,不再需要传入 PizzaFactory
对象的引用。
PizzaStore store = new 纽约特色PizzaStore();
Pizza pizza = store.orderPizza("奶酪");
2.1.2.5 订购流程
完整的订购流程如下:
PizzaStore store = new 纽约特色PizzaStore();
Pizza pizza = store.orderPizza("奶酪");
Pizza pizza = createPizza("奶酪");
// 由子类创建具体披萨return new 纽约特色奶酪Pizza();
pizza.prepare();
- 准备:面团、酱料、奶酪
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
2.1.2.6 系统类图
将 Pizza
和 PizzaStore
放在一起,系统类图如下:
2.2 工厂方法定义
上述设计采用的就是“工厂方法”模式,我们来看看它的正式定义。
工厂方法(Factory Method)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类(对其它类的)的实例化延迟到其子类。
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
这里“用于创建对象的接口”,就是工厂方法(factory method),就是示例应用中的 createPizza()
。
工厂方法将对象的创建封装在子类中,使得超类中“使用对象的代码”与子类中“创建对象的代码”解耦,也就是超类不需要知道对象的实际类型是什么。
AbstractProduct
抽象产品- 定义产品对象(工厂方法所创建对象)的接口。
ConcreteProduct
具体产品- 实现
AbstractProduct
接口,会由与之匹配的具体创建者来创建其对象。
- 实现
Creator
抽象创建者(既声明工厂方法,又使用工厂方法)- 声明抽象的工厂方法
factoryMethod()
,该方法返回一个AbstractProduct
类型对象;
也可以定义一个factoryMethod()
的默认实现,它返回一个默认的ConcreteProduct
对象; - 在
anOperation()
方法中,通过调用factoryMethod()
来创建一个AbstractProduct
类型对象,并根据其接口定义执行操作。
- 声明抽象的工厂方法
ConcreteCreator
具体创建者- 实现/覆盖工厂方法
factoryMethod()
,创建一个ConcreteProduct
对象,并以AbstractProduct
类型返回。
- 实现/覆盖工厂方法
模式优缺点:
- 优点:创建者
Creator
仅处理AbstractProduct
接口,可以与任何具体产品ConcreteProduct
对象协作。 - 缺点:为了创建一个特定的
ConcreteProduct
对象,就不得不创建一个ConcreteCreator
。
延伸阅读:《设计模式:可复用面向对象软件的基础》 3.3 Factory Method(工厂方法)— 对象创建型模式 [P81-89]
2.3 示例代码
2.3.1 Java 示例
Pizza
抽象类定义:
// Pizza.java
public abstract class Pizza {
String name; // 名称
String dough; // 面团
String sauce; // 酱料
ArrayList<String> toppings = new ArrayList<String>(); // 各种顶层配料
void prepare() {
System.out.println("准备:" + name);
System.out.println(" 擀揉面团:" + dough);
System.out.println(" 添加酱料:" + sauce);
System.out.println(" 添加顶层配料:");
for (String topping : toppings) {
System.out.println(" - " + topping);
}
}
void bake() { System.out.println("在 350 华氏度下烘烤 25 分钟。"); }
void cut() { System.out.println("将披萨切成三角形片。"); }
final void box() { System.out.println("将披萨放入对象村披萨官方的盒子中。"); }
public String getName() { return name; }
}
Pizza
具体类定义:
// NYStyleCheesePizza.java
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "纽约特色奶酪披萨";
dough = "薄饼面团";
sauce = "意式番茄酱";
toppings.add("雷吉亚诺奶酪碎");
}
}
// NYStyleClamPizza.java
public class NYStyleClamPizza extends Pizza {
public NYStyleClamPizza() {
name = "纽约特色蛤蜊披萨";
dough = "薄饼面团";
sauce = "意式番茄酱";
toppings.add("雷吉亚诺奶酪碎");
toppings.add("来自长岛海峡的新鲜蛤蜊");
}
}
// ChicagoStyleCheesePizza.java
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "芝加哥特色奶酪披萨";
dough = "厚饼面团";
sauce = "李子番茄酱";
toppings.add("马苏里拉奶酪碎");
}
void cut() { System.out.println("将披萨切成方形片。"); }
}
// ChicagoStyleClamPizza.java
public class ChicagoStyleClamPizza extends Pizza {
public ChicagoStyleClamPizza() {
name = "芝加哥特色蛤蜊披萨";
dough = "厚饼面团";
sauce = "李子番茄酱";
toppings.add("马苏里拉奶酪碎");
toppings.add("来自切萨皮克湾的冷冻蛤蜊");
}
void cut() { System.out.println("将披萨切成方形片。"); }
}
PizzaStore
框架定义:
// PizzaStore.java
public abstract class PizzaStore {
abstract Pizza createPizza(String type);
public final Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- 制作" + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
地区特色PizzaStore
定义:
// NYPizzaStore.java
public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String type) {
if (type.equals("奶酪")) {
return new NYStyleCheesePizza();
} else if (type.equals("蛤蜊")) {
return new NYStyleClamPizza();
}
return null;
}
}
// ChicagoPizzaStore.java
public class ChicagoPizzaStore extends PizzaStore {
Pizza createPizza(String type) {
if (type.equals("奶酪")) {
return new ChicagoStyleCheesePizza();
} else if (type.equals("蛤蜊")) {
return new ChicagoStyleClamPizza();
}
return null;
}
}
测试代码:
// ObjectvillePizza.java
public class ObjectvillePizza {
public static void main(String[] args) {
// 创建两个不同的店
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
// 分别在不同的店订购披萨
Pizza pizza = nyStore.orderPizza("奶酪");
System.out.println("Ethan 订购了一份" + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("蛤蜊");
System.out.println("Joel 订购了一份" + pizza.getName() + "\n");
}
}
2.3.2 C++11 示例
Pizza
抽象类定义:
struct Pizza {
virtual ~Pizza() = default;
virtual void prepare() {
std::cout << "准备:" << name << "\n";
std::cout << " 擀揉面团:" << dough << "\n";
std::cout << " 添加酱料:" << sauce << "\n";
std::cout << " 添加顶层配料:\n";
for (const auto& topping : toppings) {
std::cout << " - " << topping << "\n";
}
}
virtual void bake() { std::cout << "在 350 华氏度下烘烤 25 分钟。\n"; }
virtual void cut() { std::cout << "将披萨切成三角形片。\n"; }
virtual void box() final { std::cout << "将披萨放入对象村披萨官方的盒子中。\n"; }
std::string getName() const { return name; }
protected:
Pizza() = default;
std::string name; // 名称
std::string dough; // 面团
std::string sauce; // 酱料
std::vector<std::string> toppings; // 各种顶层配料
};
Pizza
具体类定义:
struct NYStyleCheesePizza : public Pizza {
NYStyleCheesePizza() {
name = "纽约特色奶酪披萨";
dough = "薄饼面团";
sauce = "意式番茄酱";
toppings.push_back("雷吉亚诺奶酪碎");
}
};
struct NYStyleClamPizza : public Pizza {
NYStyleClamPizza() {
name = "纽约特色蛤蜊披萨";
dough = "薄饼面团";
sauce = "意式番茄酱";
toppings.push_back("雷吉亚诺奶酪碎");
toppings.push_back("来自长岛海峡的新鲜蛤蜊");
}
};
struct ChicagoStyleCheesePizza : public Pizza {
ChicagoStyleCheesePizza() {
name = "芝加哥特色奶酪披萨";
dough = "厚饼面团";
sauce = "李子番茄酱";
toppings.push_back("马苏里拉奶酪碎");
}
void cut() override { std::cout << "将披萨切成方形片。\n"; }
};
struct ChicagoStyleClamPizza : public Pizza {
ChicagoStyleClamPizza() {
name = "芝加哥特色蛤蜊披萨";
dough = "厚饼面团";
sauce = "李子番茄酱";
toppings.push_back("马苏里拉奶酪碎");
toppings.push_back("来自切萨皮克湾的冷冻蛤蜊");
}
void cut() override { std::cout << "将披萨切成方形片。\n"; }
};
PizzaStore
框架定义:
struct PizzaStore {
virtual ~PizzaStore() = default;
virtual std::unique_ptr<Pizza> createPizza(const std::string& type) = 0;
virtual std::unique_ptr<Pizza> orderPizza(const std::string& type) final {
std::unique_ptr<Pizza> pizza = createPizza(type);
std::cout << "--- 制作" << pizza->getName() << " ---\n";
pizza->prepare();
pizza->bake();
pizza->cut();
pizza->box();
return pizza;
}
};
地区特色PizzaStore
定义:
struct NYPizzaStore : public PizzaStore {
std::unique_ptr<Pizza> createPizza(const std::string& type) override {
if (type == "奶酪") {
return std::unique_ptr<Pizza>(new NYStyleCheesePizza());
} else if (type == "蛤蜊") {
return std::unique_ptr<Pizza>(new NYStyleClamPizza());
}
return nullptr;
}
};
struct ChicagoPizzaStore : public PizzaStore {
std::unique_ptr<Pizza> createPizza(const std::string& type) override {
if (type == "奶酪") {
return std::unique_ptr<Pizza>(new ChicagoStyleCheesePizza());
} else if (type == "蛤蜊") {
return std::unique_ptr<Pizza>(new ChicagoStyleClamPizza());
}
return nullptr;
}
};
测试代码:
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// 在这里添加相关接口和类的定义
int main() {
// 创建两个不同的店
auto nyStore = std::make_shared<NYPizzaStore>();
auto chicagoStore = std::make_shared<ChicagoPizzaStore>();
// 分别在不同的店订购披萨
auto pizza = nyStore->orderPizza("奶酪");
std::cout << "Ethan 订购了一份" << pizza->getName() << "\n\n";
pizza = chicagoStore->orderPizza("蛤蜊");
std::cout << "Joel 订购了一份" << pizza->getName() << "\n\n";
}
3 OO 原则:依赖倒置
设计原则(Design Principle)
依赖抽象,不依赖具体类。
Depend upon abstractions. Do not depend upon concrete classes.
看起来很像“针对接口编程,不针对实现编程”,对吗?
At first, this principle sounds a lot like “Program to an interface, not an implementation,” right?
确实很像,只是,依赖倒置原则更强调“抽象”,建议高层组件不应该依赖于低层组件,它们都应该依赖于“抽象”(接口或抽象类)。
It is similar; however, the Dependency Inversion Principle makes an even stronger statement about abstraction. It suggests that our high-level components should not depend on our low-level components; rather, they should both depend on abstractions.
3.1 原则的应用
在示例应用中,PizzaStore
类是高层组件,各种Pizza
类是低层组件。(划分依据是:高层组件负责协调和管理低层组件)
在初始版本中,由于 PizzaStore
需要创建 各种Pizza
的实例,所以依赖关系如下:
如果 某种Pizza
类改变(增加/移除),那么就需要修改 PizzaStore
类,也就是 PizzaStore
类依赖于 各种Pizza
类,也就是“高层组件依赖于低层组件”。
(虽然在 orderPizza()
方法中,通过接口类型来定义变量 Pizza pizza
,也就是针对接口编程,但这并不会改善当前的依赖关系。)
在应用工厂模式后,PizzaStore
不再需要创建 各种Pizza
的实例,依赖关系如下:
现在,PizzaStore
只依赖于抽象类 Pizza
;由于 各种Pizza
需要实现 Pizza
接口,所以也依赖于抽象类 Pizza
。
也就是,高层组件和低层组件都依赖于抽象,符合依赖倒置原则。
“依赖倒置”原则中的“倒置”指的是对传统依赖关系的反转。传统的设计方式中,高层组件依赖于低层组件的具体实现。依赖倒置原则通过引入抽象层,将这种依赖关系反转,使得低层组件依赖于高层抽象,整体上高层组件和低层组件都依赖于抽象接口,而不是具体实现。
The “inversion” in the name Dependency Inversion Principle is there because it inverts the way you typically might think about your OO design. The low-level components now depend on a higher-level abstraction. Likewise, the high-level component is also tied to the same abstraction. So, the top-to-bottom dependency chart has inverted itself, with both high-level and low-level modules now depending on the abstraction.
遵循依赖倒置原则:高层组件通过接口创建低层组件,通过接口与低层组件交互,降低了与低层组件之间的耦合度;低层组件依据接口规范进行具体实现,具有相同接口的不同低层组件可以在不改变高层组件的情况下进行替换,提高了系统的灵活性和可扩展性。
3.2 原则遵循指南
以下指南可以帮助我们避免 OO 设计违反依赖倒置原则:
- 变量不应该持有具体类的引用。
No variable should hold a reference to a concrete class.- 问题:如果使用
new
,就会(持有到具体类的引用?/ 依赖具体类?)。 - 建议:使用工厂模式来避免。
- 问题:如果使用
- 类不应该派生自具体类。
No class should derive from a concrete class.- 问题:如果派生自具体类,就会依赖具体类。
- 建议:派生自一个抽象(接口或抽象类)。
- 方法不应该覆盖其任何基类的已实现方法。
No method should override an implemented method of any of its base classes.- 问题:如果覆盖已实现的方法,那么基类就不是一个真正适合被继承的抽象。
- 建议:基类中这些已实现的方法,应该由所有的子类共享。
【质疑】等一下,如果遵循这些指南,恐怕连最简单的程序都写不出来!
【回答】是的,任何Java程序都有违反这些指南的地方!
- 像许多原则一样,这个指南应该尽量遵循,但不要当成任何时候都应该遵循的铁律。
- 如果把这些指南融会贯通,那么
- 当违反原则时,我们会知道在违反原则,并且会有一个好的理由:
- 例如,实例化具体类
String
对象,就违反原则,但是可以这么做,因为String
类不会变化。
- 例如,实例化具体类
- 另一方面,如果一个类有可能变化,可以用一些良好的技巧(如工厂模式)来封装变化。
- 当违反原则时,我们会知道在违反原则,并且会有一个好的理由:
4 抽象工厂
4.1 对应新需求:原料工厂
对象村披萨成功的关键在于新鲜、高品质的原料,但是一些加盟店为了降低成本、增加利润使用了劣质原料。
为了维护品牌形象,现在需要建造一家原料工厂,为所有加盟店配送原料。
4.1.1 创建原料工厂
不同地区有不同特色的披萨,同样也有不同的原料需求。
原料 | 纽约特色 | 芝加哥特色 | 加州特色 | 其它特色 |
---|---|---|---|---|
面团 | 薄饼面团 | 厚饼面团 | 极薄饼面团 | … |
酱料 | 意式番茄酱 | 李子番茄酱 | 布鲁斯凯塔酱 | … |
奶酪 | 雷吉亚诺奶酪 | 马苏里拉奶酪 | 山羊干酪 | … |
蛤蜊 | 新鲜蛤蜊 | 冷冻蛤蜊 | 新鲜蛤蜊 | … |
香肠 | 意式辣味香肠 | 意式辣味香肠 | … | … |
蔬菜 | 大蒜 、洋葱 、蘑菇 、红辣椒 | 黑橄榄 、红辣椒 、菠菜 | … | … |
我们再把创建原料的代码放在“简单工厂”的演练场上观察一下:
public class Simple原料Factory {
public 面团 create面团(String region) {
if (region.equals("纽约")) { return new 薄饼面团(); }
else if (region.equals("芝加哥")) { return new 厚饼面团(); }
return null;
}
public 酱料 create酱料(String region) {
if (region.equals("纽约")) { return new 意式番茄酱(); }
else if (region.equals("芝加哥")) { return new 李子番茄酱(); }
return null;
}
public 奶酪 create奶酪(String region) {
if (region.equals("纽约")) { return new 雷吉亚诺奶酪(); }
else if (region.equals("芝加哥")) { return new 马苏里拉奶酪(); }
return null;
}
public 蛤蜊 create蛤蜊(String region) {
if (region.equals("纽约")) { return new 新鲜蛤蜊(); }
else if (region.equals("芝加哥")) { return new 冷冻蛤蜊(); }
return null;
}
public 香肠 create香肠(String region) {
if (region.equals("纽约")) { return new 意式辣味香肠(); }
else if (region.equals("芝加哥")) { return new 意式辣味香肠(); }
return null;
}
public 蔬菜[] create蔬菜(String region) {
if (region.equals("纽约")) {
return new Veggies[] { new 大蒜(), new 洋葱(), new 蘑菇(), new 红辣椒() };
} else if (region.equals("芝加哥")) {
return new Veggies[] { new 黑橄榄(), new 红辣椒(), new 菠菜() };
}
return new Veggies[] {};
}
}
思考一下:
- 当增加新的地区特色时,例如"加州特色",代码需要怎样修改?
- 在调整某个地区特色的原料品类时,例如为"芝加哥特色"增加“茄子”,代码需要怎样修改?
很明显,上述变更都需要修改 Simple原料Factory
类,代码没有对修改关闭。
为此,我们可以
- 遵循“封装变化”原则,对每个地区特色进行封装;
- 遵循“针对接口编程”原则,为所有地区特色定义统一的接口。
4.1.2 原料工厂类图
于是,我们拥有了多家原料工厂:
4.1.3 原料工厂的定义
其中 纽约特色原料Factory
的定义如下:
public class 纽约特色原料Factory implements 原料Factory {
public 面团 create面团() { return new 薄饼面团(); }
public 酱料 create酱料() { return new 意式番茄酱(); }
public 奶酪 create奶酪() { return new 雷吉亚诺奶酪(); }
public 蛤蜊 create蛤蜊() { return new 新鲜蛤蜊(); }
public 香肠 create香肠() { return new 意式辣味香肠(); }
public 蔬菜[] create蔬菜() {
return new Veggies[] { new 大蒜(), new 洋葱(), new 蘑菇(), new 红辣椒() };
}
}
刚刚,我们又复习了一遍前面 披萨工厂的分析过程。
4.1.4 披萨的类图
有了原料工厂之后,在制作披萨时,就需要通过工厂来获取原料。
- 对于抽象类
Pizza
- 定义各种原料实例变量,用于保存工厂创建的原料;
- 将
prepare()
声明为抽象方法,用于通过原料工厂创建原料; - 其它方法保持不变。
- 定义具体类
奶酪Pizza
- 定义抽象类型
原料Factory
的实例变量factory
,用于保存原料工厂的引用;- 当
factory
是纽约特色原料Factory
对象的引用时,创建的就是纽约特色披萨的原料,制作出来的也就是纽约特色的披萨; - 因为地区特色由实例变量
factory
决定,所以不再需要单独定义纽约特色奶酪Pizza
、芝加哥特色奶酪Pizza
等类。
- 当
- 实现
prepare()
方法,通过factory
创建原料。
- 定义抽象类型
香肠Pizza
、蛤蜊Pizza
、素食Pizza
与奶酪Pizza
一致,只是prepare()
创建的原料不同。
4.1.5 披萨的定义
定义 奶酪Pizza
和 蛤蜊Pizza
的代码如下:
public class 奶酪Pizza extends Pizza {
原料Factory factory;
public 奶酪Pizza(原料Factory factory) {
this.factory = factory;
}
void prepare() {
dough = factory.create面团();
sauce = factory.create酱料();
cheese = factory.create奶酪();
}
}
public class 蛤蜊Pizza extends Pizza {
原料Factory factory;
public 蛤蜊Pizza(原料Factory factory) {
this.factory = factory;
}
void prepare() {
dough = factory.create面团();
sauce = factory.create酱料();
cheese = factory.create奶酪();
clam = factory.create蛤蜊();
}
}
4.1.6 加盟店的定义
披萨的原料来自哪家工厂,由披萨店决定。更新后的 纽约特色PizzaStore
如下:
public class 纽约特色PizzaStore extends PizzaStore {
public Pizza createPizza(String type) {
// 新版代码有两处变化:
// 1. 披萨店需要创建与之匹配的原料工厂,并将其传递给 具体Pizza 类
// 2. 通过为 具体Pizza 类指定原料工厂来处理地区差异,而不是通过定义 某某地区Pizza 类
原料Factory factory = new 纽约特色原料Factory();
Pizza pizza = null;
if (type.equals("奶酪")) { pizza = new 奶酪Pizza(factory); }
else if (type.equals("香肠")) { pizza = new 香肠Pizza(factory); }
else if (type.equals("蛤蜊")) { pizza = new 蛤蜊Pizza(factory); }
else if (type.equals("素食")) { pizza = new 素食Pizza(factory); }
return pizza;
}
}
4.1.7 订购披萨
订购 纽约特色奶酪Pizza
的方式保持不变:
PizzaStore store = new 纽约特色PizzaStore();
Pizza pizza = store.orderPizza("奶酪");
4.1.8 订购流程
完整的订购流程如下:
PizzaStore store = new 纽约特色PizzaStore();
Pizza pizza = store.orderPizza("奶酪");
Pizza pizza = createPizza("奶酪");
// 由子类创建具体披萨(工厂方法)原料Factory factory = new 纽约特色原料Factory();
Pizza pizza = new 奶酪Pizza(factory);
this.factory = factory;
return pizza;
pizza.prepare();
dough = factory.create面团();
// 由工厂创建具体原料(抽象工厂)return new 薄饼面团();
sauce = factory.create酱料();
return new 意式番茄酱();
cheese = factory.create奶酪();
return new 雷吉亚诺奶酪();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
4.1.9 系统类图
将 原料Factory
、各种原料
以及它们的一位用户 蛤蜊Pizza
放在一起,得到的类图如下:
蛤蜊Pizza
为工厂的用户- 定义接口类型的
factory
实例变量,蛤蜊Pizza
不知道factory
的实际类型(与之解耦); - 定义接口类型的
各种原料
实例变量,蛤蜊Pizza
不知道各种原料
的实际类型(与之解耦); - 在
prepare()
方法中,根据接口定义,通过factory
创建各种原料
,以及执行各种原料
的相应操作。
- 定义接口类型的
面团
、酱料
等抽象原料
接口- 定义原料接口。
薄饼面团
、意式番茄酱
等具体原料
类- 实现原料接口。
原料Factory
为 抽象工厂 接口- 定义工厂接口,用于创建“一系列的”
抽象原料
类型对象; - 声明抽象的
create原料()
方法,用于创建某个抽象原料
类型对象;
因为这是一个用于“创建对象”的方法,所以被称为 工厂方法。
- 定义工厂接口,用于创建“一系列的”
纽约特色原料Factory
等具体工厂类- 实现
create原料()
方法,使用“与当前工厂匹配的”具体原料
类创建对象,并以抽象原料
类型返回。
- 实现
4.2 抽象工厂定义
上述设计采用的就是“抽象工厂”模式,它的正式定义如下:
抽象工厂(Abstract Factory)
提供一个接口,以创建一系列相关或相互依赖的对象,而无须指定它们具体的类。
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
在示例应用中,蛤蜊Pizza
使用抽象 原料Factory
提供的接口,可以创建一系列相关的 抽象原料
对象,而不需要了解 具体原料
类是什么。
AbstractFactory
抽象工厂- 定义一个抽象工厂接口,用于创建抽象产品对象(返回抽象类型)。
ConcreteFactory
具体工厂- 定义一个具体工厂,实现
AbstractFactory
接口,实现创建具体产品对象的方法。
- 定义一个具体工厂,实现
AbstractProduct
抽象产品- 定义一个抽象产品接口,声明某一类产品对象的方法签名。
ConcreteProduct
具体产品- 定义一个具体产品,实现
AbstractProduct
接口,会由与之匹配的具体工厂来创建其对象。
- 定义一个具体产品,实现
Client
用户- 仅使用
AbstractFactory
和AbstractProduct
接口; - 当实例变量
factory
是不同ConcreteFactory
对象的引用时,就会创建不同的ConcreteProduct
对象。
- 仅使用
模式优缺点:
- 优点:将用户与具体产品类分离;
- 优点:易于切换产品系列,只需要改变具体工厂;
- 优点:有利于产品的一致性,因为通过一个工厂只能创建同一个系列中的对象。
- 缺点:难以支持新种类的产品,因为需要修改
AbstractFactory
类及其所有子类。
延伸阅读:《设计模式:可复用面向对象软件的基础》 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 [P66-74]
4.3 示例代码
4.3.1 Java 示例
原料定义:
// Dough.java
public interface Dough {
public String toString();
}
// ThinCrustDough.java
public class ThinCrustDough implements Dough {
public String toString() { return "薄饼面团"; }
}
// ThickCrustDough.java
public class ThickCrustDough implements Dough {
public String toString() { return "厚饼面团"; }
}
// Sauce.java
public interface Sauce {
public String toString();
}
// MarinaraSauce.java
public class MarinaraSauce implements Sauce {
public String toString() { return "意式番茄酱"; }
}
// PlumTomatoSauce.java
public class PlumTomatoSauce implements Sauce {
public String toString() { return "李子番茄酱"; }
}
// Cheese.java
public interface Cheese {
public String toString();
}
// ReggianoCheese.java
public class ReggianoCheese implements Cheese {
public String toString() { return "雷吉亚诺奶酪碎"; }
}
// MozzarellaCheese.java
public class MozzarellaCheese implements Cheese {
public String toString() { return "马苏里拉奶酪碎"; }
}
// Clams.java
public interface Clams {
public String toString();
}
// FreshClams.java
public class FreshClams implements Clams {
public String toString() { return "来自长岛海峡的新鲜蛤蜊"; }
}
// FrozenClams.java
public class FrozenClams implements Clams {
public String toString() { return "来自切萨皮克湾的冷冻蛤蜊"; }
}
原料工厂定义:
// PizzaIngredientFactory.java
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Clams createClam();
}
// NYPizzaIngredientFactory.java
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() { return new ThinCrustDough(); }
public Sauce createSauce() { return new MarinaraSauce(); }
public Cheese createCheese() { return new ReggianoCheese(); }
public Clams createClam() { return new FreshClams(); }
}
// ChicagoPizzaIngredientFactory.java
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() { return new ThickCrustDough(); }
public Sauce createSauce() { return new PlumTomatoSauce(); }
public Cheese createCheese() { return new MozzarellaCheese(); }
public Clams createClam() { return new FrozenClams(); }
}
披萨定义:
// Pizza.java
public abstract class Pizza {
String name; // 名称
Dough dough; // 面团
Sauce sauce; // 酱料
Cheese cheese; // 奶酪
Clams clam; // 蛤蜊
abstract void prepare();
void bake() { System.out.println("在 350 华氏度下烘烤 25 分钟。"); }
void cut() { System.out.println("将披萨切成三角形片。"); }
final void box() { System.out.println("将披萨放入对象村披萨官方的盒子中。"); }
void setName(String name) { this.name = name; }
public String getName() { return name; }
public String toString() {
StringBuffer result = new StringBuffer();
result.append("---- " + name + " ----\n");
if (dough != null) { result.append(dough + "\n"); }
if (sauce != null) { result.append(sauce + "\n"); }
if (cheese != null) { result.append(cheese + "\n"); }
if (clam != null) { result.append(clam + "\n"); }
return result.toString();
}
}
// CheesePizza.java
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
System.out.println("准备:" + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
// ClamPizza.java
public class ClamPizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public ClamPizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
System.out.println("准备:" + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
clam = ingredientFactory.createClam();
}
}
披萨店定义:
// PizzaStore.java
public abstract class PizzaStore {
protected abstract Pizza createPizza(String type);
public final Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- 制作一份:" + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
// NYPizzaStore.java
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (type.equals("奶酪")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("纽约特色奶酪披萨");
} else if (type.equals("蛤蜊")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("纽约特色蛤蜊披萨");
}
return pizza;
}
}
// ChicagoPizzaStore.java
public class ChicagoPizzaStore extends PizzaStore {
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new ChicagoPizzaIngredientFactory();
if (type.equals("奶酪")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("芝加哥特色奶酪披萨");
} else if (type.equals("蛤蜊")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("芝加哥特色蛤蜊披萨");
}
return pizza;
}
}
测试代码:
// ObjectvillePizza.java
public class ObjectvillePizza {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("奶酪");
System.out.println("Ethan 订购了一份:" + pizza + "\n");
pizza = chicagoStore.orderPizza("蛤蜊");
System.out.println("Joel 订购了一份:" + pizza + "\n");
}
}
4.3.2 C++11 示例
原料定义:
struct Dough {
virtual ~Dough() = default;
virtual std::string toString() const = 0;
};
struct ThinCrustDough : public Dough {
std::string toString() const override { return "薄饼面团"; }
};
struct ThickCrustDough : public Dough {
std::string toString() const override { return "厚饼面团"; }
};
struct Sauce {
virtual ~Sauce() = default;
virtual std::string toString() const = 0;
};
struct MarinaraSauce : public Sauce {
std::string toString() const override { return "意式番茄酱"; }
};
struct PlumTomatoSauce : public Sauce {
std::string toString() const override { return "李子番茄酱"; }
};
struct Cheese {
virtual ~Cheese() = default;
virtual std::string toString() const = 0;
};
struct ReggianoCheese : public Cheese {
std::string toString() const override { return "雷吉亚诺奶酪碎"; }
};
struct MozzarellaCheese : public Cheese {
std::string toString() const override { return "马苏里拉奶酪碎"; }
};
struct Clams {
virtual ~Clams() = default;
virtual std::string toString() const = 0;
};
struct FreshClams : public Clams {
std::string toString() const override { return "来自长岛海峡的新鲜蛤蜊"; }
};
struct FrozenClams : public Clams {
std::string toString() const override { return "来自切萨皮克湾的冷冻蛤蜊"; }
};
原料工厂定义:
struct PizzaIngredientFactory {
virtual ~PizzaIngredientFactory() = default;
virtual std::unique_ptr<Dough> createDough() = 0;
virtual std::unique_ptr<Sauce> createSauce() = 0;
virtual std::unique_ptr<Cheese> createCheese() = 0;
virtual std::unique_ptr<Clams> createClam() = 0;
};
struct NYPizzaIngredientFactory : public PizzaIngredientFactory {
std::unique_ptr<Dough> createDough() override { return std::unique_ptr<Dough>(new ThinCrustDough()); }
std::unique_ptr<Sauce> createSauce() override { return std::unique_ptr<Sauce>(new MarinaraSauce()); }
std::unique_ptr<Cheese> createCheese() override { return std::unique_ptr<Cheese>(new ReggianoCheese()); }
std::unique_ptr<Clams> createClam() override { return std::unique_ptr<Clams>(new FreshClams()); }
};
struct ChicagoPizzaIngredientFactory : public PizzaIngredientFactory {
std::unique_ptr<Dough> createDough() override { return std::unique_ptr<Dough>(new ThickCrustDough()); }
std::unique_ptr<Sauce> createSauce() override { return std::unique_ptr<Sauce>(new PlumTomatoSauce()); }
std::unique_ptr<Cheese> createCheese() override { return std::unique_ptr<Cheese>(new MozzarellaCheese()); }
std::unique_ptr<Clams> createClam() override { return std::unique_ptr<Clams>(new FrozenClams()); }
};
披萨定义:
struct Pizza {
virtual ~Pizza() = default;
virtual void prepare() = 0;
virtual void bake() const { std::cout << "在 350 华氏度下烘烤 25 分钟。\n"; }
virtual void cut() const { std::cout << "将披萨切成三角形片。\n"; }
virtual void box() const final { std::cout << "将披萨放入对象村披萨官方的盒子中。\n"; }
void setName(const std::string &name) { this->name = name; }
std::string getName() const { return name; }
protected:
std::string name;
std::unique_ptr<Dough> dough; // 面团
std::unique_ptr<Sauce> sauce; // 酱料
std::unique_ptr<Cheese> cheese; // 奶酪
std::unique_ptr<Clams> clam; // 蛤蜊
friend std::ostream &operator<<(std::ostream &os, const Pizza &pizza);
};
std::ostream &operator<<(std::ostream &os, const Pizza &pizza) {
os << "---- " << pizza.name << " ----\n";
if (pizza.dough) os << pizza.dough->toString() << '\n';
if (pizza.sauce) os << pizza.sauce->toString() << '\n';
if (pizza.cheese) os << pizza.cheese->toString() << '\n';
if (pizza.clam) os << pizza.clam->toString() << '\n';
return os;
}
struct CheesePizza : public Pizza {
CheesePizza(std::shared_ptr<PizzaIngredientFactory> factory) : ingredientFactory(factory) {}
void prepare() override {
std::cout << "准备:" << name << '\n';
dough = ingredientFactory->createDough();
sauce = ingredientFactory->createSauce();
cheese = ingredientFactory->createCheese();
}
private:
std::shared_ptr<PizzaIngredientFactory> ingredientFactory;
};
struct ClamPizza : public Pizza {
ClamPizza(std::shared_ptr<PizzaIngredientFactory> factory) : ingredientFactory(factory) {}
void prepare() override {
std::cout << "准备:" << name << '\n';
dough = ingredientFactory->createDough();
sauce = ingredientFactory->createSauce();
cheese = ingredientFactory->createCheese();
clam = ingredientFactory->createClam();
}
private:
std::shared_ptr<PizzaIngredientFactory> ingredientFactory;
};
披萨店定义:
struct PizzaStore {
virtual ~PizzaStore() = default;
virtual std::unique_ptr<Pizza> orderPizza(const std::string &type) final {
auto pizza = createPizza(type);
std::cout << "--- 制作一份:" << pizza->getName() << " ---\n";
pizza->prepare();
pizza->bake();
pizza->cut();
pizza->box();
return pizza;
}
protected:
virtual std::unique_ptr<Pizza> createPizza(const std::string &type) = 0;
};
struct NYPizzaStore : public PizzaStore {
protected:
std::unique_ptr<Pizza> createPizza(const std::string &type) override {
auto ingredientFactory = std::make_shared<NYPizzaIngredientFactory>();
std::unique_ptr<Pizza> pizza;
if (type == "奶酪") {
pizza.reset(new CheesePizza(ingredientFactory));
pizza->setName("纽约特色奶酪披萨");
}
else if (type == "蛤蜊") {
pizza.reset(new ClamPizza(ingredientFactory));
pizza->setName("纽约特色蛤蜊披萨");
}
return pizza;
}
};
struct ChicagoPizzaStore : public PizzaStore {
protected:
std::unique_ptr<Pizza> createPizza(const std::string &type) override {
auto ingredientFactory = std::make_shared<ChicagoPizzaIngredientFactory>();
std::unique_ptr<Pizza> pizza;
if (type == "奶酪") {
pizza.reset(new CheesePizza(ingredientFactory));
pizza->setName("芝加哥特色奶酪披萨");
}
else if (type == "蛤蜊") {
pizza.reset(new ClamPizza(ingredientFactory));
pizza->setName("芝加哥特色蛤蜊披萨");
}
return pizza;
}
};
测试代码:
#include <iostream>
#include <memory>
#include <string>
// 在这里添加相关接口和类的定义
int main() {
auto nyStore = std::make_shared<NYPizzaStore>();
auto chicagoStore = std::make_shared<ChicagoPizzaStore>();
auto pizza = nyStore->orderPizza("奶酪");
std::cout << "Ethan 订购了一份" << *pizza << "\n\n";
pizza = chicagoStore->orderPizza("蛤蜊");
std::cout << "Joel 订购了一份" << *pizza << "\n\n";
}
5 工厂方法 vs 抽象工厂
5.1 模式对比
回顾 工厂方法 和 抽象工厂 的定义,对比如下:
模式 | 工厂方法 | 抽象工厂 |
---|---|---|
定义类型 | 定义抽象方法 | 定义抽象接口 |
核心功能 | 封装对象的创建: 在不指定具体类的情况下创建对象,并以抽象类型返回。 | 相同 |
模式意图 | 让类将产品对象的创建延迟到子类 | 创建一系列相关或相互依赖的对象 |
对象数量 | 单一对象 | 一系列对象 |
方法数量 | 一个 | 一系列(通常每类对象一个) |
方法实现 | 由子类实现 | 相同 |
使用方式 | 继承方式 | 组合方式(组合工厂对象) |
抽象工厂定义了一系列的抽象方法,每个方法由子类(具体工厂)提供实现,这种情况下的方法与工厂方法的定义一致。
所以,在抽象工厂模式中,具体工厂经常通过“实现工厂方法”来创建对象。
前文提到 问题1的解决方案是否体现了某种设计模式的思想,虽然披萨工厂只创建一类对象,但是,因为使用组合方式,所以采用的也是抽象工厂模式。
5.2 应用对比
在示例中,模式的应用主要围绕3个问题展开,它们具有相同结构,通过下表链接可以对比查看。
采用模式 | 抽象工厂 | 工厂方法 | 抽象工厂 |
---|---|---|---|
问题描述 | 2.1.1 不同地区具有不同特色 | 2.1.2 部分加盟店流程不规范 | 4.1.1 创建原料工厂 |
工厂类图 | 2.1.1.1 披萨工厂类图 | 2.1.2.1 披萨店类图 | 4.1.2 原料工厂类图 |
工厂定义 | 2.1.1.2 披萨工厂的定义 | 2.1.2.3 加盟店的定义 | 4.1.3 原料工厂的定义 |
工厂用户 | 2.1.1.3 披萨店的定义 | 2.1.2.2 披萨店的定义 | 4.1.5 披萨的定义 |
4.1.6 加盟店的定义 | |||
系统调用 | 2.1.1.4 订购披萨 | 2.1.2.4 订购披萨 | 4.1.7 订购披萨 |
处理流程 | 2.1.1.5 订购流程 | 2.1.2.5 订购流程 | 4.1.8 订购流程 |
模式应用 | 2.1.1.6 系统类图 | 2.1.2.6 系统类图 | 4.1.9 系统类图 |
6 设计工具箱
6.1 OO 基础
OO 基础回顾
- 抽象(Abstraction)
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
6.2 OO 原则
6.2.1 新原则
依赖抽象,不依赖具体类。
Depend on abstractions. Do not depend on concrete classes.
6.2.2 原则回顾
- 封装变化。
Encapsulate what varies. - 针对接口编程,而不是针对实现编程。
Program to interfaces, not implementations. - 优先使用组合,而不是继承。
Favor composition over inheritance. - 尽量做到交互对象之间的松耦合设计。
Strive for loosely coupled designs between objects that interact. - 类应该对扩展开放,对修改关闭。
Classes should be open for extension, but closed for modification.
6.3 OO 模式
6.3.1 新模式
- 工厂方法(Factory Method)
- 定义了一个创建对象的接口,但由子类决定要实例化哪个类。
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.
- 定义了一个创建对象的接口,但由子类决定要实例化哪个类。
- 抽象工厂(Abstract Factory)
- 提供一个接口,创建相关或依赖对象的家族,而不需要指定具体类。
The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- 提供一个接口,创建相关或依赖对象的家族,而不需要指定具体类。
6.3.2 模式回顾
- 策略模式(Strategy Pattern)
- 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. - 让算法的变化独立于使用算法的客户。
Strategy lets the algorithm vary independently from clients that use it.
- 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
- 观察者模式(Observer Pattern)
- 定义对象之间的一对多依赖,
The Observer Pattern defines a one-to-many dependency between objects - 这样一来,当一个对象改变状态时,它的所有依赖者都会被通知并自动更新。
so that when one object changes state, all of its dependents are notified and updated automatically.
- 定义对象之间的一对多依赖,
- 装饰者模式(Decorator Pattern)
- 动态地给一个对象添加一些额外的职责。
The Decorator Pattern attaches additional responsibilities to an object dynamically. - 就增加功能来说,装饰者模式相比生成子类更为灵活。
Decorators provide a flexible alternative to subclassing for extending functionality.
- 动态地给一个对象添加一些额外的职责。
参考
- [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
- [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
- wickedlysmart: Head First设计模式 Java 源码
Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.