设计模式课程第5讲(比较大的实例)—— CAD/CAM + 电子商务综合案例

主讲:王亚沙(北京大学软件研究所) 课程:0C107 设计模式 来源:田浩然上传的资料

内容概览

本讲通过两个完整的大型案例,演示如何综合运用多种设计模式解决真实系统设计问题,并介绍了变化分析矩阵(Variation Analysis Matrix) 这一系统化的需求分析方法。

三大主题:

  1. CAD/CAM 工件信息抽取模块—— Bridge + Facade + Adapter 三模式组合
  2. 变化分析矩阵—— 一种系统化的共性/变化性分析方法
  3. 电子商务订单处理系统—— Strategy + Abstract Factory 双模式综合

一、CAD/CAM 系统案例

问题背景

系统由三部分组成:

  • CAD/CAM 系统(已有系统,存在 V1、V2 等多个版本,未来可能还有 V3、V4)
  • 专家系统(已有系统,稳定不变,修改难度巨大)
  • 工件信息抽取模块(需要开发的中间模块)

核心难点:

  • 多套 CAD/CAM 系统并存,数据格式各异
  • 专家系统接口要求稳定,不能随 CAD 系统变化而变化
  • 需要在多版本 CAD 系统与稳定的专家系统之间建立桥梁

CAD/CAM 存储的模型

5 种几何特征(feature):

特征加工工具
hole(孔)钻头
cutout(冲切)冲床
slot(槽)铣刀
special(特殊形状)冲床
irregular(不规则形)多种工具组合

关键术语

术语英文含义
几何特征Geometry钣金件的形状描述:各特征的位置、尺寸、外形
零件Part钣金件本身,需要存储其几何信息
数据集/模型Dataset / modelCAD/CAM 数据库中存储零件几何信息的记录集
数控机床/数控集NC machine / NC set数控机床,由计算机程序控制切割头,程序由命令组成

直观 OO 设计的问题

最初的设计思路:

  • 按形状分类(hole / cutout / slot / special / irregular)
  • 每种形状再按 V1 / V2 系统分类
  • V1 中 feature 功能委派给一个 V1System 方法聚合体对象
  • V2 中 feature 功能委派给一组 OOGxx 对象

问题:类爆炸(5 种形状 × N 个系统版本),且每新增一个 CAD 系统版本就要新增一整套类。

用模式方法改进设计

采用 Alexander《建筑永恒之道》提出的一次一模式方法:

  1. 找出所有可用的模式
  2. 按”为其他模式提供背景”的顺序排列
  3. 逐个应用模式,每一步扩展设计
  4. 重复直到全部模式应用完毕
  5. 添加细节

最终设计:三模式组合

最终设计运用了三个核心设计模式,按层次从底到顶:

专家系统(稳定接口)
    │
    ▼
┌───────────┐
│  Facade   │  ← 为专家系统提供统一、简单、稳定的高层接口
└─────┬─────┘
      │
┌─────▼─────┐
│  Adapter  │  ← 将不同 CAD 系统的接口适配成统一接口
└─────┬─────┘
      │
┌─────▼─────┐
│  Bridge   │  ← 将"特征种类"与"CAD系统版本"两个维度解耦
└───────────┘
      │
   CAD/CAM 系统(多版本)

1. Bridge 模式—— 核心解耦

将两个独立变化的维度(特征种类 × CAD 系统版本)分离:

  • 抽象层(Abstraction):Feature 层次结构(HoleFeature、CutoutFeature、SlotFeature…)
  • 实现层(Implementor):CADSystem 层次结构(V1System、V2System、V3System…)

两个维度可以独立扩展:

  • 新增特征 → 只需新增 Feature 子类
  • 新增 CAD 版本 → 只需新增 CADSystem 子类
  • 避免了类爆炸(5 × N → 5 + N)

2. Adapter 模式—— 接口适配

为每种 CAD 系统创建适配器,将各异的原生接口转换为 Bridge 模式中 Implementor 定义的统一接口:

  • V1Adapter:封装 V1 系统的方法聚合体接口
  • V2Adapter:封装 V2 系统的 OOG 对象组接口
  • V3Adapter:未来新增 V3 时只需加一个适配器

3. Facade 模式—— 统一入口

为专家系统提供一个简单、稳定、高层的统一接口,屏蔽底层 Bridge + Adapter 的复杂性:

  • 专家系统只与 Facade 交互
  • 底层新增 CAD 系统或修改实现,不影响专家系统
  • 符合”专家系统稳定不变”的约束

设计收益

维度直观设计模式组合设计
类数量5 × N(类爆炸)5 + N(线性增长)
新增 CAD 版本新增 5 个类新增 1 个 Adapter 类
专家系统影响可能受接口变化影响Facade 层隔离,完全不受影响
可测试性各版本耦合难测试可独立测试各层
可维护性改动影响面大单一职责,改动局部化

二、变化分析矩阵

为什么需要变化分析矩阵

“当我们精心设计了一组概念后,常常发现仍然存在特殊情况不符合上述概念。”

  • 现实世界的变化点极多,人脑无法全部记住和分辨
  • OO 设计需要做共性与变化性分析:
    • 共性:变化发生的地点(抽象层)
    • 变化:共性概念的具体实例(实现层)
  • 客户通常谈得很具体,不习惯在概念层次表达
  • 客户说”总是”通常表示”通常”,说”从不”通常表示”很少”

矩阵结构

            变化1(实现A)   变化2(实现B)   变化3(实现C)
共性概念1      具体实现         具体实现         具体实现
共性概念2      具体实现         具体实现         具体实现
共性概念3      具体实现         具体实现         具体实现
  • 行:一个变化点(共性概念),以及实现这个变化点的多种具体情况
  • 列:一个具体的场景/实现方案

使用步骤

  1. 从一个具体场景开始:找到该场景中最重要的特性,填入矩阵
  2. 扩展新场景:继续处理其他情况,每种情况独立分析,扩展矩阵
  3. 补充新概念:用新的变化点(行)扩展矩阵
  4. 横向归纳(按行):从每行中识别共性,抽象出策略/接口
  5. 纵向归纳(按列):从每列中识别一致性约束,抽象出工厂/族
  6. 匹配设计模式:
    • 行 → Strategy 模式(封装算法族)
    • 列 → Abstract Factory 模式(保证产品族一致性)
  7. 得出高层设计

与客户沟通的经验法则

对于非常具体的问题,客户详细的回答一般是可信的;但他们一般性的回答却不可信。

  • 客户非常了解问题域
  • 客户不习惯在概念层次表达,谈得很具体
  • “总是” = 通常,“从不” = 很少
  • 追问具体场景,不要轻信概括性陈述

三、电子商务系统案例

问题需求

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

需求项美国加拿大
运费计算UPS 费率联邦快递海运费率
地址验证美国邮政规则加拿大邮政规则
税费计算州税 + 地税GST 国税 + PST 地税
支付货币美元加元
发货方式美国邮政联邦快递

后续可能扩展到德国、英国等更多国家。

应用变化分析矩阵:八步法

第一步:填入第一个场景(美国)

共性概念美国客户
计算运费使用 UPS 费率
验证地址使用美国邮政的规则
计算税费美国州税和地税
支付货币美元

第二步:填入第二个场景(加拿大)

共性概念美国客户加拿大客户
计算运费使用 UPS 费率使用联邦快运的海运费率
验证地址使用美国邮政的规则使用加拿大邮政的规则
计算税费美国州税和地税使用 GST 和 PST
支付货币美元加元

第三步:扩展场景和特征(加入德国)

共性概念美国客户加拿大客户德国客户
计算运费UPS 费率联邦快运海运费率德国快运海运费率
验证地址美国邮政规则加拿大邮政规则德国邮政规则
计算税费州税+地税GST + PST德国 VAT
支付货币美元加元德国马克
日期格式mm/dd/yyyydd/mm/yyyydd/mm/yyyy
最大重量30kg——

第四步:按行识别规律(抽象策略)

每一行代表一个变化点,可用策略封装:

共性概念抽象
计算运费ShippingStrategy 接口,封装运费计算规则
验证地址AddressValidator 接口,封装地址验证规则
计算税费TaxStrategy 接口,封装税费计算规则
支付货币Money 对象,包含货币名称和总量,自动转换
日期Time 对象,按用户 locale 提供不同显示格式
最大重量WeightLimitStrategy 接口,封装限重规则

→ 这些行对应 Strategy 模式。

第五步:按列识别约束(产品族)

每一列的对象必须配套使用(美国的运费算法必须配美国的地址验证、美国的税制、美元):

  • 美国产品族:USShipping + USAddressValidator + USTax + USD
  • 加拿大产品族:CAShipping + CAAddressValidator + CATax + CAD
  • 德国产品族:DEShipping + DEAddressValidator + DETax + DEM

→ 这些列对应 Abstract Factory 模式。

第六步:匹配设计模式(行视角)

每一行 → Strategy 模式:

  • 封装”做同一件事的不同方法”
  • 算法族可独立于使用它的客户端而变化

第七步:匹配设计模式(列视角)

每一列 → Abstract Factory 模式:

  • 保证同一产品族中对象的一致性(美国的工厂生产的全是美国规则的对象)
  • 切换产品族只需切换工厂

第八步:综合得出高层设计

OrderProcessor(订单处理器)
    │
    ▼
┌──────────────────┐
│ OrderConfigFactory  ←  Abstract Factory 接口
└────────┬─────────┘
         │
   ┌─────┴──────┬──────────┐
   ▼            ▼          ▼
USFactory   CAFactory   DEFactory
   │            │          │
   ▼            ▼          ▼
ShippingStrategy  ←  Strategy 接口
AddressValidator  ←  Strategy 接口
TaxStrategy       ←  Strategy 接口
Money / Time      ←  值对象

设计收益

  • 新增一个国家:新增一个具体工厂类 + 各策略的具体实现类,不影响现有代码(OCP)
  • 新增一种策略(如新增”退货政策”):在抽象工厂接口加一个创建方法,各具体工厂实现,符合 OCP 但需改所有工厂(权衡点)
  • 运行时切换国家:只需切换工厂对象
  • 一致性保证:同一工厂生产的所有对象天然配套,不会出现”美国运费 + 加拿大税制”的错误组合

核心要点总结

  1. 设计模式不是孤立使用的:真实项目中常以组合形式出现
  2. 一次一模式方法:按”背景提供顺序”逐步应用模式,而非一次性全上
  3. Bridge + Adapter + Facade 层次:
    • Bridge 负责两个维度的解耦(最底层)
    • Adapter 负责接口适配(中间层)
    • Facade 负责为客户端提供统一入口(最顶层)
  4. 变化分析矩阵:从具体场景出发,按行/列两个维度归纳,自动推导出 Strategy + Abstract Factory 的组合
  5. 横向看策略,纵向看工厂:矩阵的行是 Strategy,列是 Abstract Factory
  6. 客户沟通法则:信具体、不信概括;信场景、不信结论

关联笔记