设计模式综合实例(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步:找出第一个场景中最重要的特性,组织成矩阵(只有一列)
- 第2步:加入第二个场景,扩展矩阵(第二列)
- 第3步:继续加入新场景,发现新的共性概念(新增行),扩展矩阵
- 第4步:从行的角度找规律——每一行代表一种变化点
- 第5步:用概念命名行——“这些具体实现是 XXX 的不同版本”
- 第6步:从行的角度找设计模式——每行通常对应 Strategy 模式
- 第7步:从列的角度找设计模式——每列的对象需要保持一致性,通常用 Abstract Factory
- 第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)
- 每一步都验证设计是否合理
关联笔记
- 设计模式原则(第3讲) — SRP/OCP/LSP/DIP/ISP 五大原则
- 设计模式 Pattern 1(第4讲) — Façade / Adapter / Strategy / Bridge / Abstract Factory
- 设计模式 比较大的实例(第5讲) — CAD/CAM 工件信息抽取 + 电子商务订单系统(早期版)
- 设计模式 Pattern 2(第6讲) — Decorator / Observer / Template Method
- 设计模式 Pattern 2(第6讲)补充 — Decorator / Observer / Template Method