当前位置: 首页 > article >正文

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

使用 SimplePizzaFactoryorderPizza() 如下:

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
orderPizza() : CallCreatePizzaOfFactory
SimplePizzaFactory
createPizza()
«abstract»
Pizza
prepare()
bake()
cut()
box()
奶酪Pizza
香肠Pizza
蛤蜊Pizza
素食Pizza
  1. PizzaStore 是工厂的 用户
    • 组合 SimplePizzaFactory 类型对象 factory,用于创建 Pizza 类型对象;
      注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallCreatePizzaOfFactory
  2. SimplePizzaFactory 是创建 Pizza工厂
    • 定义 createPizza() 方法,经常被声明为静态类型,应该是应用中唯一引用 具体Pizza 类的地方。
  3. Pizza 是工厂创建的 抽象产品,定义为抽象类;
    • 为产品定义统一的接口(这里的“接口”是广义的“接口概念”)
    • 为部分方法提供一般性的实现,可以被子类覆盖。
  4. 奶酪Pizza香肠Pizza蛤蜊Pizza素食Pizza具体产品 类;
    • 继承抽象产品类 Pizza,实现必要的方法。

2 工厂方法

2.1 对应新需求:连锁加盟店

对象村披萨店生意很好,已经成功塑造“对象村披萨”品牌形象,并展开连锁经营,很多商家希望加盟。
但是面临两个问题:

  1. 加盟店位于不同的地区,而不同地区的披萨有着不同的特色,包括纽约的薄皮披萨、芝加哥的深盘披萨,以及加州的薄脆披萨等等;
  2. 部分加盟店的流程不够规范,例如有时会忘记切片、使用第三方包装盒等等。

对于上述问题,我们来逐一分析下,首先是问题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;
    }
}

思考一下:

  1. 当增加新的地区特色时,例如"加州特色",代码需要怎样修改?
  2. 在调整某个地区特色的披萨品类时,例如为"纽约特色"增加“希腊披萨”,代码需要怎样修改?

很明显,上述变更都需要修改 SimplePizzaFactory 类,代码没有对修改关闭。(看起来,简单工厂不适合创建容易发生变更的多组对象)

为此,我们可以

  1. 遵循“封装变化”原则,对每个地区特色进行封装;
  2. 遵循“针对接口编程”原则,为所有地区特色定义统一的接口。

2.1.1.1 披萨工厂类图

于是,我们拥有了多家披萨工厂:

«interface»
PizzaFactory
createPizza()
纽约特色PizzaFactory
createPizza()
芝加哥特色PizzaFactory
createPizza()
加州特色PizzaFactory
createPizza()

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("奶酪");
    1. Pizza pizza = factory.createPizza("奶酪"); // 由工厂类创建具体披萨
      • return new 纽约特色奶酪Pizza();
    2. pizza.prepare();
      • 准备:面团、酱料、奶酪
    3. pizza.bake();
    4. pizza.cut();
    5. pizza.box();
    6. return pizza;

2.1.1.6 系统类图

PizzaFactoryPizzaPizzaStore 放在一起,系统类图如下:

«interface»
PizzaFactory
createPizza()
纽约特色PizzaFactory
createPizza()
芝加哥特色PizzaFactory
createPizza()
«abstract»
Pizza
prepare()
bake()
cut()
box()
纽约特色奶酪Pizza
纽约特色蛤蜊Pizza
芝加哥特色奶酪Pizza
芝加哥特色蛤蜊Pizza
PizzaStore
PizzaFactory factory
orderPizza() : CallCreatePizzaOfFactory

现在可以通过不同的工厂来创建不同地区特色的披萨,问题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 披萨店类图

«abstract»
PizzaStore
createPizza()
orderPizza()
pizzaMenu()
纽约特色PizzaStore
createPizza()
芝加哥特色PizzaStore
createPizza()
加州特色PizzaStore
createPizza()
  1. PizzaStore 抽象类:
    • 声明 createPizza() 抽象方法,用于创建具有不同地区特色的披萨;
      因为这是一个用于“创建对象”的方法,所以将其称为 工厂方法
    • 定义 orderPizza() 方法,并声明为 final,禁止被子类覆盖;
      可以确保所有子类行为的一致性。
    • 定义 pizzaMenu() 方法,提供默认实现;
      子类可以根据需要进行覆盖,以实现创新行为。
  2. 地区特色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("奶酪");
    1. Pizza pizza = createPizza("奶酪"); // 由子类创建具体披萨
      • return new 纽约特色奶酪Pizza();
    2. pizza.prepare();
      • 准备:面团、酱料、奶酪
    3. pizza.bake();
    4. pizza.cut();
    5. pizza.box();
    6. return pizza;

2.1.2.6 系统类图

PizzaPizzaStore 放在一起,系统类图如下:

«abstract»
Pizza
prepare()
bake()
cut()
box()
纽约特色奶酪Pizza
芝加哥特色奶酪Pizza
纽约特色蛤蜊Pizza
芝加哥特色蛤蜊Pizza
«abstract»
PizzaStore
createPizza()
orderPizza() : CallCreatePizza
纽约特色PizzaStore
createPizza()
芝加哥特色PizzaStore
createPizza()

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()

工厂方法将对象的创建封装在子类中,使得超类中“使用对象的代码”与子类中“创建对象的代码”解耦,也就是超类不需要知道对象的实际类型是什么。

«abstract»
AbstractProduct
ConcreteProduct
«abstract»
Creator
factoryMethod()
anOperation() : CreateProductByFactoryMethod
ConcreteCreator
factoryMethod() : ReturnConcreteProduct
  1. AbstractProduct 抽象产品
    • 定义产品对象(工厂方法所创建对象)的接口。
  2. ConcreteProduct 具体产品
    • 实现 AbstractProduct 接口,会由与之匹配的具体创建者来创建其对象。
  3. Creator 抽象创建者(既声明工厂方法,又使用工厂方法)
    • 声明抽象的工厂方法 factoryMethod(),该方法返回一个 AbstractProduct 类型对象;
      也可以定义一个 factoryMethod() 的默认实现,它返回一个默认的 ConcreteProduct 对象;
    • anOperation() 方法中,通过调用 factoryMethod() 来创建一个 AbstractProduct 类型对象,并根据其接口定义执行操作。
  4. 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 的实例,所以依赖关系如下:

PizzaStore
奶酪Pizza
希腊Pizza
香肠Pizza

如果 某种Pizza 类改变(增加/移除),那么就需要修改 PizzaStore 类,也就是 PizzaStore 类依赖于 各种Pizza 类,也就是“高层组件依赖于低层组件”。

(虽然在 orderPizza() 方法中,通过接口类型来定义变量 Pizza pizza,也就是针对接口编程,但这并不会改善当前的依赖关系。)

在应用工厂模式后,PizzaStore 不再需要创建 各种Pizza 的实例,依赖关系如下:

PizzaStore
«abstract»
Pizza
奶酪Pizza
香肠Pizza
蛤蜊Pizza
素食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 设计违反依赖倒置原则:

  1. 变量不应该持有具体类的引用。
    No variable should hold a reference to a concrete class.
    • 问题:如果使用 new,就会(持有到具体类的引用?/ 依赖具体类?)。
    • 建议:使用工厂模式来避免。
  2. 类不应该派生自具体类。
    No class should derive from a concrete class.
    • 问题:如果派生自具体类,就会依赖具体类。
    • 建议:派生自一个抽象(接口或抽象类)。
  3. 方法不应该覆盖其任何基类的已实现方法。
    No method should override an implemented method of any of its base classes.
    • 问题:如果覆盖已实现的方法,那么基类就不是一个真正适合被继承的抽象。
    • 建议:基类中这些已实现的方法,应该由所有的子类共享。

【质疑】等一下,如果遵循这些指南,恐怕连最简单的程序都写不出来!
【回答】是的,任何Java程序都有违反这些指南的地方!

  • 像许多原则一样,这个指南应该尽量遵循,但不要当成任何时候都应该遵循的铁律。
  • 如果把这些指南融会贯通,那么
    • 当违反原则时,我们会知道在违反原则,并且会有一个好的理由:
      • 例如,实例化具体类 String 对象,就违反原则,但是可以这么做,因为 String 类不会变化。
    • 另一方面,如果一个类有可能变化,可以用一些良好的技巧(如工厂模式)来封装变化。

4 抽象工厂

4.1 对应新需求:原料工厂

对象村披萨成功的关键在于新鲜、高品质的原料,但是一些加盟店为了降低成本、增加利润使用了劣质原料。
为了维护品牌形象,现在需要建造一家原料工厂,为所有加盟店配送原料。

4.1.1 创建原料工厂

不同地区有不同特色的披萨,同样也有不同的原料需求。

Chicago vs NewYork

原料纽约特色芝加哥特色加州特色其它特色
面团薄饼面团厚饼面团极薄饼面团
酱料意式番茄酱李子番茄酱布鲁斯凯塔酱
奶酪雷吉亚诺奶酪马苏里拉奶酪山羊干酪
蛤蜊新鲜蛤蜊冷冻蛤蜊新鲜蛤蜊
香肠意式辣味香肠意式辣味香肠
蔬菜大蒜洋葱蘑菇红辣椒黑橄榄红辣椒菠菜

我们再把创建原料的代码放在“简单工厂”的演练场上观察一下:

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[] {};
    }
}

思考一下:

  1. 当增加新的地区特色时,例如"加州特色",代码需要怎样修改?
  2. 在调整某个地区特色的原料品类时,例如为"芝加哥特色"增加“茄子”,代码需要怎样修改?

很明显,上述变更都需要修改 Simple原料Factory 类,代码没有对修改关闭。

为此,我们可以

  1. 遵循“封装变化”原则,对每个地区特色进行封装;
  2. 遵循“针对接口编程”原则,为所有地区特色定义统一的接口。

4.1.2 原料工厂类图

于是,我们拥有了多家原料工厂:

«interface»
原料Factory
create面团()
create酱料()
create奶酪()
create蛤蜊()
create香肠()
create蔬菜()
纽约特色原料Factory
create面团()
create酱料()
create奶酪()
create蛤蜊()
create香肠()
create蔬菜()
芝加哥特色原料Factory
create面团()
create酱料()
create奶酪()
create蛤蜊()
create香肠()
create蔬菜()
加州特色原料Factory
create面团()
create酱料()
create奶酪()
create蛤蜊()
create香肠()
create蔬菜()

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 披萨的类图

有了原料工厂之后,在制作披萨时,就需要通过工厂来获取原料。

«abstract»
Pizza
面团 dough
酱料 sauce
奶酪 cheese
蛤蜊 clam
香肠 pepperoni
蔬菜 veggies[]
prepare()
bake()
cut()
box()
奶酪Pizza
原料Factory factory
prepare()
香肠Pizza
原料Factory factory
prepare()
蛤蜊Pizza
原料Factory factory
prepare()
素食Pizza
原料Factory factory
prepare()
  1. 对于抽象类 Pizza
    • 定义各种原料实例变量,用于保存工厂创建的原料;
    • prepare() 声明为抽象方法,用于通过原料工厂创建原料;
    • 其它方法保持不变。
  2. 定义具体类 奶酪Pizza
    • 定义抽象类型 原料Factory 的实例变量 factory,用于保存原料工厂的引用;
      • factory纽约特色原料Factory 对象的引用时,创建的就是纽约特色披萨的原料,制作出来的也就是纽约特色的披萨;
      • 因为地区特色由实例变量 factory 决定,所以不再需要单独定义 纽约特色奶酪Pizza芝加哥特色奶酪Pizza 等类。
    • 实现 prepare() 方法,通过 factory 创建原料。
  3. 香肠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("奶酪");
    1. Pizza pizza = createPizza("奶酪"); // 由子类创建具体披萨(工厂方法)
      • 原料Factory factory = new 纽约特色原料Factory();
      • Pizza pizza = new 奶酪Pizza(factory);
        • this.factory = factory;
      • return pizza;
    2. pizza.prepare();
      • dough = factory.create面团(); // 由工厂创建具体原料(抽象工厂)
        • return new 薄饼面团();
      • sauce = factory.create酱料();
        • return new 意式番茄酱();
      • cheese = factory.create奶酪();
        • return new 雷吉亚诺奶酪();
    3. pizza.bake();
    4. pizza.cut();
    5. pizza.box();
    6. return pizza;

4.1.9 系统类图

原料Factory各种原料 以及它们的一位用户 蛤蜊Pizza 放在一起,得到的类图如下:

纽约
纽约
纽约
纽约
芝加哥
芝加哥
芝加哥
芝加哥
«interface»
原料Factory
create面团()
create酱料()
create奶酪()
create蛤蜊()
create香肠()
create蔬菜()
纽约特色原料Factory
create面团()
create酱料()
create奶酪()
create蛤蜊()
create香肠()
create蔬菜()
芝加哥特色原料Factory
create面团()
create酱料()
create奶酪()
create蛤蜊()
create香肠()
create蔬菜()
«interface»
面团
薄饼面团
厚饼面团
«interface»
酱料
意式番茄酱
李子番茄酱
«interface»
奶酪
雷吉亚诺奶酪
马苏里拉奶酪
«interface»
蛤蜊
新鲜蛤蜊
冷冻蛤蜊
蛤蜊Pizza
原料Factory factory
面团 dough
酱料 sauce
奶酪 cheese
蛤蜊 clam
prepare() : CallCreateXXOfFactory
  1. 蛤蜊Pizza 为工厂的用户
    • 定义接口类型的 factory 实例变量,蛤蜊Pizza 不知道 factory 的实际类型(与之解耦);
    • 定义接口类型的 各种原料 实例变量,蛤蜊Pizza 不知道 各种原料 的实际类型(与之解耦);
    • prepare() 方法中,根据接口定义,通过 factory 创建 各种原料,以及执行 各种原料 的相应操作。
  2. 面团酱料抽象原料 接口
    • 定义原料接口。
  3. 薄饼面团意式番茄酱具体原料
    • 实现原料接口。
  4. 原料Factory抽象工厂 接口
    • 定义工厂接口,用于创建“一系列的” 抽象原料 类型对象;
    • 声明抽象的 create原料() 方法,用于创建某个 抽象原料 类型对象;
      因为这是一个用于“创建对象”的方法,所以被称为 工厂方法
  5. 纽约特色原料Factory 等具体工厂类
    • 实现 create原料() 方法,使用“与当前工厂匹配的” 具体原料 类创建对象,并以 抽象原料 类型返回。

4.2 抽象工厂定义

上述设计采用的就是“抽象工厂”模式,它的正式定义如下:

抽象工厂(Abstract Factory)
提供一个接口,以创建一系列相关或相互依赖的对象,而无须指定它们具体的类。
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

在示例应用中,蛤蜊Pizza 使用抽象 原料Factory 提供的接口,可以创建一系列相关的 抽象原料 对象,而不需要了解 具体原料 类是什么。

«interface»
AbstractFactory
createProductA()
createProductB()
ConcreteFactory1
createProductA()
createProductB()
ConcreteFactory2
createProductA()
createProductB()
«interface»
AbstractProductA
ConcreteProductA1
ConcreteProductA2
«interface»
AbstractProductB
ConcreteProductB1
ConcreteProductB2
Client
AbstractFactory factory
  1. AbstractFactory 抽象工厂
    • 定义一个抽象工厂接口,用于创建抽象产品对象(返回抽象类型)。
  2. ConcreteFactory 具体工厂
    • 定义一个具体工厂,实现 AbstractFactory 接口,实现创建具体产品对象的方法。
  3. AbstractProduct 抽象产品
    • 定义一个抽象产品接口,声明某一类产品对象的方法签名。
  4. ConcreteProduct 具体产品
    • 定义一个具体产品,实现 AbstractProduct 接口,会由与之匹配的具体工厂来创建其对象。
  5. Client 用户
    • 仅使用 AbstractFactoryAbstractProduct 接口;
    • 当实例变量 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 基础回顾

  1. 抽象(Abstraction)
  2. 封装(Encapsulation)
  3. 继承(Inheritance)
  4. 多态(Polymorphism)

6.2 OO 原则

6.2.1 新原则

依赖抽象,不依赖具体类。
Depend on abstractions. Do not depend on concrete classes.

6.2.2 原则回顾

  1. 封装变化。
    Encapsulate what varies.
  2. 针对接口编程,而不是针对实现编程。
    Program to interfaces, not implementations.
  3. 优先使用组合,而不是继承。
    Favor composition over inheritance.
  4. 尽量做到交互对象之间的松耦合设计。
    Strive for loosely coupled designs between objects that interact.
  5. 类应该对扩展开放,对修改关闭。
    Classes should be open for extension, but closed for modification.

6.3 OO 模式

6.3.1 新模式

  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.
  2. 抽象工厂(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 模式回顾

  1. 策略模式(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.
  2. 观察者模式(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.
  3. 装饰者模式(Decorator Pattern)
    • 动态地给一个对象添加一些额外的职责。
      The Decorator Pattern attaches additional responsibilities to an object dynamically.
    • 就增加功能来说,装饰者模式相比生成子类更为灵活。
      Decorators provide a flexible alternative to subclassing for extending functionality.

参考

  1. [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
  2. [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
  3. wickedlysmart: Head First设计模式 Java 源码

Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.


http://www.kler.cn/a/458415.html

相关文章:

  • 【论文笔记】Top-nσ: Not All Logits Are You Need
  • 游戏引擎学习第65天
  • “校园疫情防控的技术支持”:疫情管控系统的实现与评估
  • WPF系列四:图形控件Rectangle
  • 【代码分析】Unet-Pytorch
  • 【每日学点鸿蒙知识】hvigor升级、Dialog动画、LocalStorage无效、页面与子组件的生命周期、cookie设置
  • GNN图神经网络模型详解与代码复现
  • 正点原子串口例程解读
  • Ollama+OpenWebUI+llama3本地部署
  • 跟着问题学3.1——R-CNN模型详解
  • Spring创建异步线程,使用@Async注解时不指定value可以吗?
  • IT6622: HDMI 1.4 Tx with eARC RX and Embedded MCU
  • 【视觉SLAM:一、初识SLAM】
  • Pytorch知识框架梳理
  • C# 语法糖集锦
  • 【每日学点鸿蒙知识】子窗口方向、RichEdit不居中、本地资源缓存给web、Json转对象丢失方法、监听状态变量数组中内容改变
  • dede-cms关于shell漏洞
  • Unity3D Huatuo技术原理剖析详解
  • 修改RuoYi框架,并添加新项目
  • 实现一个iOS晃动动画