1. 设计模式综合运用与对比解析
作为一名有十年经验的软件工程师,我经常被问到:"设计模式在实际项目中真的有用吗?"今天我就通过一个坦克大战的案例,带大家看看多种设计模式如何协同工作,以及它们之间的微妙差异。
1.1 项目概述与设计思路
这个坦克大战项目虽然需求简单(只有两种坦克型号),但正是这种简洁性让我们能专注于模式本身。B70和B50两种坦克的主要区别在于射程和速度:
| 坦克类型 | 射程 | 速度 |
|---|---|---|
| B70 | 70米 | 70公里/小时 |
| B50 | 50米 | 50公里/小时 |
核心设计思路是:
- 使用策略模式处理不同坦克型号
- 通过装饰模式构建功能调用链
- 采用享元模式共享相同功能对象
- 利用命令模式封装功能操作为对象
提示:选择坦克大战作为示例是因为它足够简单,能让我们把注意力集中在模式运用上,而不是复杂的需求理解上。
1.2 模式协同工作示意图
code复制[客户端] → [策略模式] → [命令模式]
↓ ↓
[享元模式] ← [装饰模式]
2. 核心模式实现与对比
2.1 策略模式:坦克型号的抽象
策略模式在这里用于抽象不同坦克型号的行为差异。我们定义了一个IStrategy接口和两个具体策略类:
java复制interface IStrategy {
void create();
void update(Tank t);
}
class B70Strategy implements IStrategy {
public B70Strategy(Client c) {
mSpecificationOfTank = new ConcreteSpecification(70);
myinit();
}
// 其他实现...
}
class B50Strategy implements IStrategy {
public B50Strategy(Client c) {
mSpecificationOfTank = new ConcreteSpecification(50);
myinit();
}
// 其他实现...
}
关键点:
- 通过规格参数(70/50)体现策略差异
- 符合开闭原则,新增坦克型号只需添加策略类
- 策略对象通常在整个生命周期保持不变
2.2 装饰模式 vs 职责链模式
这两个模式在结构上非常相似,都是通过对象链传递请求,但意图不同:
| 特性 | 装饰模式 | 职责链模式 |
|---|---|---|
| 目的 | 动态添加功能 | 处理请求传递 |
| 调用结果 | 功能叠加 | 可能中断或传递 |
| 对象关系 | 必须完整执行整个链 | 可选择中间节点处理 |
| 典型应用 | IO流处理 | 异常处理链 |
在我们的坦克项目中,功能调用链坦克→射击→跑→客户端更符合装饰模式,因为:
- 每个环节都在前一个功能基础上添加新行为
- 调用必须完整执行整个链
- 体现的是功能增强而非责任传递
2.3 享元模式:共享功能对象
射击和跑这两个功能对象被设计为享元:
java复制class FlyweightFactory {
static FlyweightFactory mFlyweightFactory = new FlyweightFactory();
HashMap<String,Function> mMaps = new HashMap<String,Function>();
public Function GetFlyweitht(String key) {
Function f = mMaps.get(key);
if(f == null) {
return createFlyweight(key);
}
return f;
}
// ...
}
享元模式的关键:
- 将对象分为内部状态(不变)和外部状态(可变)
- 通过工厂管理共享对象
- 适用于大量细粒度对象场景
经验:当系统中某个类的实例超过100个,且这些实例的大部分状态可以外部化时,就该考虑享元模式了。
2.4 命令模式:封装操作为对象
我们将坦克的创建和功能调用封装为命令对象:
java复制interface IHandler {
void create(Tank t);
}
abstract class Handler implements IHandler {
protected ISpecificationOfTank mSpecificationOfTank;
// ...
}
class HandlerRun extends Handler {
public void create(Tank t) {
t.mRun = new Run(mSpecificationOfTank);
mStrategy.update(t);
}
}
命令模式的三大优势:
- 将请求封装为独立对象
- 支持请求排队或日志记录
- 实现撤销/重做功能
3. 模式间的协作关系
3.1 策略与命令的协作
策略模式负责整体行为差异,命令模式负责具体操作封装:
code复制策略对象(B70/B50) → 创建命令链 → 执行具体功能
3.2 装饰与享元的结合
装饰模式构建调用链,享元模式提供共享的功能实现:
code复制装饰链:坦克→射击[享元]→跑[享元]→客户端
3.3 状态模式的可扩展设计
虽然当前项目没有直接使用状态模式,但我们可以很容易地扩展:
java复制interface TankState {
void move();
void fire();
}
class NormalState implements TankState {
public void move() { /* 正常移动 */ }
public void fire() { /* 正常开火 */ }
}
class DamagedState implements TankState {
public void move() { /* 移动速度减半 */ }
public void fire() { /* 射击精度降低 */ }
}
状态模式与策略模式的区别:
- 策略模式:算法族互换,客户端明确知道策略
- 状态模式:状态转换对客户端透明,行为随内部状态改变
4. 完整代码解析
4.1 核心类结构
java复制// 策略接口
interface IStrategy {
void create();
void update(Tank t);
}
// 命令接口
interface IHandler {
void create(Tank t);
}
// 享元工厂
class FlyweightFactory {
private static FlyweightFactory instance = new FlyweightFactory();
private Map<String, Function> pool = new HashMap<>();
public static FlyweightFactory getInstance() {
return instance;
}
public Function getFunction(String key) {
if(!pool.containsKey(key)) {
pool.put(key, createFunction(key));
}
return pool.get(key);
}
// ...
}
// 坦克类
class Tank {
Shot mShot;
Run mRun;
public void execute() {
mShot.execute();
mRun.execute();
}
}
4.2 模式协作流程
- 客户端创建具体策略(B70/B50)
- 策略对象初始化命令链
- 命令链按顺序创建坦克并装配功能
- 功能对象从享元工厂获取
- 最终形成一个装饰模式的调用链
4.3 运行结果分析
执行流程示例:
code复制创建B70坦克
射击功能执行:射程70米
移动功能执行:速度70公里/小时
创建完成通知客户端
5. 设计模式对比指南
5.1 结构相似模式对比
| 模式 | 关系结构 | 对象协作方式 | 典型应用场景 |
|---|---|---|---|
| 装饰模式 | 链表 | 功能叠加 | IO流处理 |
| 职责链模式 | 链表 | 请求传递 | 审批流程 |
| 组合模式 | 树形 | 部分-整体 | 文件系统 |
| 观察者模式 | 星型 | 一对多通知 | 事件处理系统 |
5.2 行为模式对比
| 模式 | 关键特征 | 状态管理 | 灵活性 |
|---|---|---|---|
| 策略模式 | 算法族互换 | 外部设置 | 高 |
| 状态模式 | 行为随状态变 | 内部转换 | 中等 |
| 命令模式 | 封装操作为对象 | 可保存状态 | 高 |
| 模板方法 | 固定流程可变步骤 | 子类实现 | 低 |
5.3 创建型模式对比
| 模式 | 创建方式 | 适用场景 | 复杂度 |
|---|---|---|---|
| 工厂方法 | 子类决定实例 | 单一产品族 | 低 |
| 抽象工厂 | 接口创建系列 | 多产品族 | 高 |
| 建造者 | 分步构建 | 复杂对象创建 | 中 |
| 原型 | 克隆现有对象 | 创建成本高的对象 | 中 |
| 单例 | 唯一实例 | 全局访问点 | 低 |
6. 实际应用建议
6.1 模式选择原则
- 先理解问题:不要强行套用模式,先理清需求痛点
- 简单优先:能用简单模式就不用复杂模式
- 组合使用:复杂系统通常需要多种模式协同
- 保持灵活:为未来可能的扩展留出空间
6.2 常见陷阱与规避
-
过度设计:
- 症状:为用模式而用模式
- 解法:YAGNI原则(You Aren't Gonna Need It)
-
模式混淆:
- 症状:把策略模式当状态模式用
- 解法:明确区分意图而非结构
-
性能问题:
- 症状:过多间接调用导致性能下降
- 解法:关键路径避免多层装饰/代理
6.3 性能考量
- 享元模式能显著减少内存使用
- 装饰模式会增加调用栈深度
- 观察者模式的通知可能成为瓶颈
- 频繁创建的命令对象应考虑对象池
7. 扩展思考
7.1 模式演进趋势
现代软件开发中,设计模式呈现以下趋势:
- 与函数式编程结合(如策略模式→高阶函数)
- 反应式编程中的观察者模式变体
- 微服务架构中的模式应用(如网关→外观模式)
7.2 模式与架构的关系
设计模式是架构的构建块:
- MVC架构:组合了观察者、策略、组合模式
- 微服务架构:大量使用外观、代理、网关模式
- 事件驱动架构:核心是观察者模式的扩展
7.3 学习路线建议
- 先掌握SOLID原则
- 从简单模式开始(策略、观察者、装饰)
- 通过实际项目练习模式组合
- 阅读优秀开源代码中的模式应用
- 定期回顾模式之间的关联与差异
设计模式不是银弹,但确实是工程师工具箱中不可或缺的工具。通过这个坦克大战的案例,我希望展示了模式如何协同工作来解决实际问题。记住,精通设计模式的标志不是能说出所有模式的定义,而是能在适当的时候自然而然地应用它们。