设计模式对比与选型指南:策略/状态/代理/装饰器详解
设计模式对比与选型指南:如何选择最合适的解决方案
设计模式是软件开发中解决常见问题的经典方案,但相似模式之间常常让人困惑。本文将深入分析三组易混淆的设计模式对比,并提供场景化的选型指南,帮助您在项目中做出合理选择。
一、相似模式深度对比
1. 策略模式 vs 状态模式
策略模式和状态模式在结构上非常相似,都由上下文类持有某个接口的引用,但它们的意图不同:
关键区别:
- 策略模式:算法选择是主动的、明确的,客户端知道不同策略的存在
- 状态模式:状态转换是被动的、自动的,客户端不关心当前状态
示例代码:
// 策略模式示例
interface SortingStrategy {
void sort(int[] data);
}
class QuickSort implements SortingStrategy {
public void sort(int[] data) { /* 快速排序实现 */ }
}
class Context {
private SortingStrategy strategy;
public void setStrategy(SortingStrategy strategy) {
this.strategy = strategy;
}
public void executeSort(int[] data) {
strategy.sort(data);
}
}
// 状态模式示例
interface State {
void handle(Context context);
}
class ConcreteStateA implements State {
public void handle(Context context) {
// 处理逻辑后可能自动切换到StateB
context.setState(new ConcreteStateB());
}
}
实践建议:
- 当行为需要运行时动态切换时用策略模式(如支付方式选择)
- 当对象行为随内部状态自动改变时用状态模式(如订单状态流转)
2. 代理模式 vs 装饰器模式
代理模式和装饰器模式都通过包装对象来增强功能,但目的不同:
维度 | 代理模式 | 装饰器模式 |
---|---|---|
目的 | 控制访问 | 增强功能 |
关注点 | 访问权限、延迟加载、远程调用 | 动态添加职责 |
对象创建 | 通常由代理控制 | 通常由客户端创建并装饰 |
典型应用 | Spring AOP、RPC框架 | Java I/O流、Web拦截器 |
代码对比:
// 代理模式
interface Image {
void display();
}
class RealImage implements Image {
public void display() { /* 加载并显示图片 */ }
}
class ProxyImage implements Image {
private RealImage realImage;
public void display() {
if (realImage == null) {
realImage = new RealImage(); // 延迟加载
}
realImage.display();
}
}
// 装饰器模式
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public double getCost() {
return decoratedCoffee.getCost();
}
}
class MilkDecorator extends CoffeeDecorator {
public double getCost() {
return super.getCost() + 0.5;
}
}
实践建议:
- 需要控制对象访问时用代理模式(如权限检查、缓存)
- 需要透明地添加功能时用装饰器模式(如日志记录、数据压缩)
3. 工厂方法 vs 抽象工厂
工厂方法和抽象工厂都是创建型模式,但抽象层次不同:
核心差异:
- 工厂方法:创建单一产品,通过子类决定实例化哪个类
- 抽象工厂:创建产品族,一个工厂类可以创建多个相关对象
场景对比:
// 工厂方法
interface Logger {
void log(String message);
}
class FileLogger implements Logger { /*...*/ }
abstract class LoggerFactory {
public abstract Logger createLogger();
public void log(String message) {
Logger logger = createLogger();
logger.log(message);
}
}
// 抽象工厂
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
class WinFactory implements GUIFactory {
public Button createButton() { return new WinButton(); }
public Checkbox createCheckbox() { return new WinCheckbox(); }
}
实践建议:
- 当产品类型单一且可能有多种实现时用工厂方法(如日志记录器)
- 当需要创建一组相关或依赖对象时用抽象工厂(如跨平台UI组件)
二、场景化选型指南
1. 对象创建场景选型
决策树:
是否需要精细控制构建过程?
├─ 是 → 建造者模式(如复杂DTO构造)
├─ 否 → 是否需要创建多种类型对象?
├─ 是 → 抽象工厂(产品族)或工厂方法(单一产品)
└─ 否 → 单例模式(全局唯一实例)
性能考虑:
- 单例模式:适合重量级对象(如数据库连接池)
- 原型模式:适合创建成本高的对象(通过克隆避免重复初始化)
- 建造者模式:适合参数多且有校验逻辑的对象构造
2. 行为扩展场景选型
方案对比:
需求 | 推荐模式 | 理由 |
---|---|---|
透明地添加功能 | 装饰器模式 | 保持接口一致性,动态叠加功能 |
处理链式请求 | 责任链模式 | 解耦请求发送者和多个处理者 |
需要运行时切换算法 | 策略模式 | 避免条件语句,便于扩展新策略 |
对象行为随状态改变 | 状态模式 | 消除大量条件判断,状态转换逻辑内聚 |
示例场景:
- API权限校验:责任链模式(认证→授权→限流)
- 支付处理:策略模式(微信/支付宝支付切换)
- 订单状态管理:状态模式(待支付→已支付→已发货)
3. 性能敏感场景选型
优化策略:
享元模式适用场景:
- 存在大量细粒度对象
- 对象的大部分状态可以外部化
- 示例:字符处理中的字母对象池、游戏中的粒子系统
原型模式适用场景:
- 对象创建成本高(如数据库查询结果)
- 需要快速生成对象副本
- 示例:配置模板克隆、机器学习数据集复制
性能数据参考:
模式 | 内存节省 | 创建速度 | 典型应用场景 |
---|---|---|---|
享元模式 | 70-90% | 中等 | 文字编辑器、棋牌游戏 |
原型模式 | - | 快3-5倍 | 复杂对象初始化 |
对象池模式 | 可变 | 最快 | 数据库连接、线程池 |
代码示例:
// 享元模式实现
class CharacterFlyweight {
private char intrinsicState; // 内部状态
public CharacterFlyweight(char c) {
this.intrinsicState = c;
}
public void display(Font font) { // font是外部状态
System.out.println("Character " + intrinsicState + " with font " + font);
}
}
class FlyweightFactory {
private Map<Character, CharacterFlyweight> pool = new HashMap<>();
public CharacterFlyweight getCharacter(char c) {
return pool.computeIfAbsent(c, CharacterFlyweight::new);
}
}
// 原型模式实现
class Prototype implements Cloneable {
private List<String> data;
public Prototype clone() {
try {
Prototype clone = (Prototype) super.clone();
clone.data = new ArrayList<>(this.data); // 深拷贝
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
三、综合实践建议
避免模式滥用:
- 不要为了使用模式而使用模式
- 当简单代码能解决问题时,保持简单
- 警惕"模式狂热"导致的过度设计
混合使用模式:
- 常见组合:工厂方法+单例、装饰器+责任链
- 示例:Spring框架中同时使用了工厂、代理、模板方法等多种模式
演进式设计:
- 初期可以先用简单实现
- 当变化点出现时再重构到模式
- 示例:从简单if-else开始,随业务复杂逐步重构为策略模式
性能权衡:
- 装饰器模式会增加调用栈深度
- 抽象工厂会增加类数量
- 在性能关键路径上谨慎使用复杂模式
记住,设计模式是工具而不是目标。理解每个模式的意图和适用场景,才能在恰当的时机做出正确的设计决策。在实际项目中,往往需要根据具体需求对经典模式进行适当调整或组合使用。