设计模式综合实例(CAD/CAM + 电子商务)

北京大学软件研究所 王亚沙《设计模式》课程第 7 讲 42 页,文字版 PDF

核心内容

本讲通过两个完整的综合案例,展示如何在真实项目中组合运用多种设计模式解决复杂问题,并介绍了一个非常实用的需求分析工具——变化分析矩阵。


一、CAD/CAM 工件信息抽取模块

问题背景

系统由三部分组成:

  • CAD/CAM 系统(已有系统,存在多个版本 v1、v2,未来可能有 v3、v4…)
  • 专家系统(已有系统,稳定不变,修改难度巨大)
  • 工件信息抽取模块(需要新开发的中间连接模块)

CAD/CAM 存储的模型:5 种几何特征

  • hole(钻头)
  • cutout(冲床)
  • slot(铣刀)
  • special(冲床)
  • irregular(多种工具组合)

核心难点:

  • 支持多种不同的 CAD/CAM 系统(v1、v2、未来更多版本)
  • 为专家系统提供稳定不变的使用接口
  • 本质:两边都在变化,中间层需要隔离变化

直观 OO 设计的问题

直观做法:按形状分类(hole/cutout/slot/special/irregular),每种形状再按 V1/V2 系统分类。

问题:

  • 类爆炸:5 种形状 × 2 个系统 = 10 个 feature 类,再加 V3 就是 15 个
  • V1 中各 feature 功能委派给 V1System 方法聚合体
  • V2 中各 feature 功能委派给 OOGxx 系列对象
  • 代码重复、扩展性差

模式改进设计(三模式组合)

采用 Alexander《建筑永恒之道》的方法:一次一模式,按”为其他模式提供背景”的顺序逐步应用。

1. Bridge 模式 — 核心骨架

作用:将”形状维度”与”CAD/CAM 系统版本维度”分离,两个维度独立变化。

  • 抽象端:Feature 层次(HoleFeature、CutoutFeature、SlotFeature…)
  • 实现端:CADSystem 层次(V1System、V2System、V3System…)
  • 新增形状只需扩展 Feature 端,新增系统版本只需扩展 CADSystem 端
  • 从 N×M 个类降为 N+M 个类

2. Facade 模式 — 对外统一入口

作用:为专家系统提供一个简单、稳定的高层接口,封装底层子系统的复杂性。

  • 工件信息抽取模块对外暴露一个高层 Facade 类
  • 专家系统只与 Facade 交互,无需了解内部 Bridge、Feature、CADSystem 的复杂结构
  • 即使内部重构,Facade 接口保持稳定

3. Adapter 模式 — 兼容已有接口

作用:将已有的 V1System、V2System 等接口适配成 Bridge 所需的统一 CADSystem 接口。

  • V1 系统的接口是方法聚合体风格
  • V2 系统的接口是 OOGxx 对象风格
  • Adapter 将它们统一成 CADSystem 接口,供 Bridge 的实现端使用

三模式协作关系

专家系统 → Facade(工件信息抽取模块入口)
              ↓
         Feature 抽象层(Bridge 抽象端)
              ↓
         CADSystem 接口(Bridge 实现端)
              ↓
    ┌─────────┴─────────┐
    ↓                   ↓
V1Adapter          V2Adapter
    ↓                   ↓
V1System           V2System
(已有系统)       (已有系统)

二、变化分析矩阵(Variation Analysis Matrix)

为什么需要矩阵

  • 现实世界变化点极多,单靠人脑无法记住所有共性和变化性
  • OO 设计的核心是共性与变化性分析:
    • 共性 = 变化发生的地点(抽象类/接口)
    • 变化 = 共性概念的具体实例(具体实现类)

矩阵结构

          变化1     变化2     变化3     ……
共性概念A    实现A1    实现A2    实现A3
共性概念B    实现B1    实现B2    实现B3
共性概念C    实现C1    实现C2    实现C3
…
  • 行:一个变化点(共性概念)+ 该变化点的多种具体实现
  • 列:一个具体场景(所有变化点在该场景下的实现组合)

八步法使用流程

  1. 第1步:找出第一个场景中最重要的特性,组织成矩阵(只有一列)
  2. 第2步:加入第二个场景,扩展矩阵(第二列)
  3. 第3步:继续加入新场景,发现新的共性概念(新增行),扩展矩阵
  4. 第4步:从行的角度找规律——每一行代表一种变化点
  5. 第5步:用概念命名行——“这些具体实现是 XXX 的不同版本”
  6. 第6步:从行的角度找设计模式——每行通常对应 Strategy 模式
  7. 第7步:从列的角度找设计模式——每列的对象需要保持一致性,通常用 Abstract Factory
  8. 第8步:综合结果,得出高层设计

关于客户需求的经验

“客户通常非常了解问题域,但不会在概念层次表达,而是谈得很具体。”

  • 客户说”总是”= 通常
  • 客户说”从不”= 很少
  • 客户对具体问题的详细回答可信
  • 客户对一般性问题的回答不可信

三、电子商务订单系统

问题描述

美国电子商务公司的订单处理系统,需支持不同国家/地区的销售订单。

需求清单:

  • 加拿大和美国构建订单系统
  • 按所在国家计算运费
  • 以所在国家货币支付
  • 美国:按当地税法计算税额 + 美国邮政规则验证地址
  • 加拿大:联邦快递发货 + GST(国税)+ PST(地税)

两类场景对比

环节美国客户加拿大客户
计算运费UPS 费率联邦快运海运费率
验证地址美国邮政规则加拿大邮政规则
计算税费美国州税+地税GST + PST
支付货币美元加元

用变化分析矩阵推导设计

逐步扩展矩阵(美国→加拿大→德国),最终得到:

变化点(行)模式选择
计算运费Strategy 模式
验证地址Strategy 模式
计算税费Strategy 模式
支付货币Money 模式( Fowler 经典模式)
日期格式Time 模式(策略+格式化)
最大重量Strategy 模式
场景(列)模式选择
美国客户对象族Abstract Factory 模式
加拿大客户对象族Abstract Factory 模式
德国客户对象族Abstract Factory 模式

最终模式组合

  • 行方向(Strategy):每个变化点封装为策略,如 TaxStrategy、ShippingStrategy、AddressVerificationStrategy
  • 列方向(Abstract Factory):每个国家一个 Factory,负责创建该国的一套策略对象
    • USOrderFactory 创建 US 版的运费/地址/税费/货币对象
    • CanadaOrderFactory 创建加拿大版的一套对象
    • 新增国家只需新增一个 Factory 类
  • Money 模式:封装货币与金额,自动处理汇率转换

模式协作关系

OrderProcessor(订单处理器)
    ↓ 使用
OrderFactory(Abstract Factory 接口)
    ├── USOrderFactory → 创建 US 的整套策略
    ├── CanadaOrderFactory → 创建 Canada 的整套策略
    └── GermanyOrderFactory → 创建 Germany 的整套策略
    ↓ 创建
ShippingStrategy   TaxStrategy   AddressStrategy   Money
    ↑                 ↑                ↑              ↑
 (各国实现)      (各国实现)      (各国实现)    (统一对象)

关键洞察

1. 模式不是孤立使用的

真实项目中模式几乎总是组合使用:

  • Bridge + Facade + Adapter = 多版本系统集成
  • Strategy + Abstract Factory = 多地区/多平台业务规则
  • 模式之间有协作关系,不是简单堆砌

2. 变化分析矩阵是连接需求与设计的桥梁

  • 先有需求(场景化描述)
  • 用矩阵结构化需求,抽离共性与变化
  • 从行/列两个维度自动推导出该用什么模式
  • 是一个可重复的方法论,不靠灵感

3. “一次一模式”的设计哲学

  • 不要试图一次性把所有模式都想清楚
  • 按”为其他模式提供背景”的顺序逐步引入
  • 先搭骨架模式(Bridge、Abstract Factory),再填细节模式(Strategy、Adapter)
  • 每一步都验证设计是否合理

关联笔记