文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

深入浅出设计模式【二十四、终章———选择指南、过渡设计、实际案例、经验教训】

1. 设计模式对比与选择指南

相似模式的区别与适用场景

理解相似模式之间的微妙差别是正确选择的关键。以下是一些常见易混淆模式的对比:

模式对核心区别适用场景
策略 (Strategy) vs 状态 (State)意图不同:策略是主动选择算法,客户端明确知道不同策略的存在;状态是被动响应,状态转换由内部触发,对客户端透明。策略:支付方式、折扣计算、排序算法。
状态:订单状态机、游戏角色行为、工作流引擎。
装饰器 (Decorator) vs 代理 (Proxy)目的不同:装饰器旨在增强功能(添加新职责);代理旨在控制访问(延迟加载、权限控制、日志记录)。结构相似,意图是区分的关键。装饰器:Java I/O Streams、为对象动态添加功能。
代理:Spring AOP、远程代理、虚拟代理。
外观 (Facade) vs 中介者 (Mediator)耦合方向不同:外观是单向的简化接口,为客户端提供一个简单入口;中介者是双向的协调中心,同事对象之间通过中介者相互通信。外观:简化复杂子系统调用、API网关。
中介者:GUI组件交互、多对象间复杂通信的协调。
组合 (Composite) vs 装饰器 (Decorator)结构不同:组合构建树形结构(部分-整体);装饰器构建链式结构(包裹增强)。组合:文件系统、UI组件树、组织架构。
装饰器:动态添加功能,如I/O流处理。
观察者 (Observer) vs 发布-订阅 (Pub/Sub)耦合度不同:观察者是直接通知,观察者通常知道主题;发布-订阅是通过中介(消息代理),发布者和订阅者完全解耦,不知道对方存在。观察者:GUI事件监听、模型-视图同步。
发布-订阅:微服务间异步通信、系统集成、事件总线。
命令 (Command) vs 策略 (Strategy)粒度不同:命令封装一个请求为对象(包含接收者、操作);策略封装一个算法为对象。命令可用于实现策略。命令:实现撤销/重做、任务队列、事务。
策略:替换算法,如支付策略、计算策略。

如何根据需求选择合适模式

选择模式是一个决策过程,而非简单匹配。遵循以下决策流程可以大大提高模式选择的准确性:

Yes
No
Yes
Yes
No
No
Yes
Yes
No
No
Yes
Yes
No
分析需求与痛点
代码重复/流程固定?
考虑模板方法模式
需要扩展对象功能?
动态且透明地增强?
使用装饰器模式
考虑策略模式
对象间依赖复杂?
是双向复杂依赖?
使用中介者模式
使用观察者或发布-订阅模式
需要统一接口?
是遍历统一?
使用迭代器模式
使用适配器模式

核心原则

  1. 首先,不要使用模式: 从一个简单、清晰的实现开始。当发现它难以维护、扩展或存在重复代码时,再考虑引入模式进行重构。
  2. 识别变化点: 找到系统中可能变化的部分。模式的作用就是封装变化。将变化的部分与不变的部分分离开来。
  3. 关注意图,而非结构: 两个模式可能结构相似,但意图不同(如策略vs状态)。问自己:“我真正需要解决的是什么问题?”
  4. 考虑未来变化: 如果某个方向的变化非常频繁,就选择支持那个方向扩展的模式(如策略模式支持算法扩展,访问者模式支持操作扩展)。

模式组合使用的实践

设计模式很少单独使用,组合使用可以产生更强大的力量:

  1. 组合模式 + 访问者模式

    • 场景: 遍历一个复杂的树形结构(如AST、UI组件树),并对每个节点执行多种操作(如渲染、序列化、验证)。
    • 如何组合: 组合模式负责构建树形结构,访问者模式负责对树中的每个节点执行操作,避免了将操作代码污染到节点类中。
  2. 工厂模式 + 原型模式

    • 场景: 需要创建多种类型的复杂对象,且创建成本较高。
    • 如何组合: 工厂类内部维护一个原型对象注册表。当客户端请求新对象时,工厂不是从头创建,而是克隆(原型模式)一个已存在的原型对象,并可能进行一些初始化配置。
  3. 装饰器模式 + 责任链模式

    • 场景: 构建一个可灵活扩展的过滤器链或拦截器链(如Servlet Filter、Spring Interceptor)。
    • 如何组合: 每个过滤器都是一个装饰器,它们被组织成一条责任链。请求和响应对象沿着链条传递,每个过滤器都可以装饰(增强或过滤)它们。
  4. 观察者模式 + 中介者模式

    • 场景: 在一个事件驱动的系统中,需要集中管理复杂的事件响应逻辑。
    • 如何组合: 组件之间不直接通信,而是发布事件到中介者(事件总线)。中介者负责将事件路由给相应的观察者(事件处理器),起到了解耦和协调的作用。

2. 反模式与常见误区

常见的设计误用

  1. 单例模式 (Singleton) 的滥用

    • 误用: 将其作为全局变量桶使用,破坏了封装性,导致代码难以测试(无法模拟替代实例)。
    • 正确做法: 谨慎使用单例,仅用于表示真正的全局唯一实例(如线程池、配置管理器)。优先考虑依赖注入(DI)来管理对象的生命周期和依赖关系。
  2. 强迫使用模式

    • 误用: 在不需要的地方生搬硬套模式,使简单问题复杂化。
    • 正确做法: 遵循“最简单且能工作的方案”原则,在需要时再重构引入模式。
  3. 过度工程 (Over-Engineering)

    • 误用: 为一个可能永远不会变化的部分设计灵活的、基于模式的解决方案。
    • 正确做法: 实践YAGNI原则(You Ain’t Gonna Need It)。除非变化确实可能发生,否则不要为它做设计。

过度设计问题

过度设计通常源于对“完美架构”的追求,其代价是:

  • 复杂度飙升: 引入了大量不必要的抽象层和间接调用,使代码难以理解和调试。
  • 开发效率降低: 编写“灵活”的代码比编写“直接”的代码需要更多时间。
  • 维护成本增加: 其他开发者需要花费更多精力来理解你的设计。

如何避免: 从简单设计开始,通过持续重构来适应变化的需求。让设计随着对问题域的理解加深而自然演进。

模式适用的边界条件

每个模式都有其适用的前提,忽略这些前提会导致失败:

  • 访问者模式: 要求对象结构稳定。如果频繁添加新的元素类,就需要修改所有访问者接口,这将是灾难性的。
  • 迭代器模式: 如果集合太小且简单,直接使用 for 循环可能比创建迭代器更简单高效。
  • 享元模式: 仅在需要创建大量细粒度对象且这些对象有共享状态时才有效。否则,管理共享池的开销可能超过其收益。
  • 中介者模式: 容易演变为上帝对象,集中了所有交互逻辑,变得臃肿且难以维护。

3. 设计模式在实际项目中的应用案例

真实项目中的模式应用

案例:电商平台订单系统

模式应用场景具体实现与收益
状态模式订单生命周期管理(待支付、已支付、已发货、已完成、已取消)。每个状态一个类,封装了该状态下所有允许的操作和状态转换规则。收益: 消除了庞大的 if-else 分支,新状态易于添加,状态转换逻辑清晰。
策略模式折扣计算、支付方式选择、运费计算。为每种计算方式定义一个策略类。收益: 轻松支持促销活动(新增折扣策略),支付渠道扩容(新增支付策略),符合开闭原则。
观察者模式订单状态变更通知。订单(主题)状态改变时,自动通知观察者(如:发送短信、邮件、更新用户界面、触发积分计算)。收益: 订单核心逻辑与通知逻辑解耦,通知方式易于扩展。
工厂模式创建复杂的订单对象(可能包含商品、优惠券、用户信息等聚合)。使用工厂方法或抽象工厂封装订单对象的组装逻辑。收益: 简化客户端代码,统一创建逻辑,易于切换不同的对象构建方式。
代理模式订单服务的访问控制和日志记录。为订单服务接口创建代理,在调用真实服务前后进行权限校验和日志记录。收益: 业务逻辑与非功能性需求(安全、日志)分离,符合单一职责原则。通常通过Spring AOP实现。
门面模式提供统一的订单操作API。创建一个门面类,封装下单、支付、查询等涉及多个子系统的复杂调用。收益: 为客户端提供一个简单易用的接口,隐藏了内部微服务的复杂性。

模式带来的收益与代价

模式典型收益常见代价
大多数模式解耦提高灵活性/扩展性提高可读性/可维护性代码复用增加复杂度: 引入更多类和接口。
性能开销: 增加间接调用(通常可忽略)。
学习曲线: 新成员需要时间理解设计。
抽象工厂/工厂方法隐藏创建细节,支持产品族。添加新产品类型需修改工厂接口。
适配器让不兼容的接口协同工作。过多使用会使系统混乱,不如统一接口。
观察者实现松耦合的事件通知。通知顺序不确定,可能引发循环依赖。
访问者易于添加新操作。难以添加新元素,破坏封装性。

经验教训总结

  1. 模式是手段,不是目标: 永远以解决实际问题为出发点,而不是为了使用模式而使用模式。
  2. 重构优于预先设计: 很难在项目初期就设计出完美的模式应用。更佳实践是:先让代码工作,然后在识别出代码异味(Code Smells)时,通过重构引入模式。
  3. 理解比记忆更重要: 深刻理解模式的意图适用场景,比死记硬背其结构更重要。这样才能在正确的时机选择正确的模式。
  4. 基础设施是模式的最佳伴侣: Spring等现代框架本身就大量使用了工厂、代理、模板方法等模式。理解这些框架的工作原理,能帮助你更好地在业务代码中应用模式。
  5. 简洁至上: 最有效的设计往往是那个最简单、最直接的设计。模式的威力在于管理复杂性,而不是增加复杂性。

结论: 设计模式是资深工程师工具箱中强大的工具,但它们需要经验和判断力来正确使用。始终在简单性灵活性之间做出明智的权衡,并记住:可工作的、清晰的代码,远比充满模式但难以理解的代码更有价值。

posted @ 2025-08-30 00:22  NeoLshu  阅读(3)  评论(0)    收藏  举报  来源