1. 简单工厂模式与策略模式深度解析
在软件开发中,设计模式是解决常见问题的经典方案。简单工厂模式和策略模式都是常用的设计模式,但它们的应用场景和设计理念有着本质区别。作为一名有十年经验的Java开发者,我经常看到初学者混淆这两种模式,今天就来彻底讲清楚它们的区别。
简单工厂模式关注的是对象的创建过程,而策略模式关注的是算法的动态切换。简单来说,简单工厂帮你决定"用哪个对象",策略模式让你决定"怎么用这个对象"。这两种模式在实际项目中都非常有用,但用错了场景就会导致代码难以维护。
2. 简单工厂模式详解
2.1 核心设计思想
简单工厂模式的核心在于将对象的创建逻辑集中管理。想象你去餐厅点餐,你只需要告诉服务员"我要一份牛排",而不需要关心牛排是怎么做的、由哪个厨师来做。服务员(工厂)会根据你的需求,决定让哪个厨师(具体类)来准备这道菜。
在代码层面,简单工厂模式包含三个关键角色:
- 抽象产品(Operation接口)
- 具体产品(AddOperation等实现类)
- 工厂类(OperationFactory)
这种模式最大的好处是客户端代码与具体实现解耦。客户端不需要知道具体创建了哪个类的实例,只需要通过工厂获取对象并使用。
2.2 典型实现代码分析
让我们深入分析计算器示例的Java实现。首先定义运算接口:
java复制public interface Operation {
double calculate(double a, double b);
}
然后实现具体的运算类,比如加法:
java复制public class AddOperation implements Operation {
@Override
public double calculate(double a, double b) {
return a + b;
}
}
工厂类的实现是关键:
java复制public class OperationFactory {
public static Operation createOperation(String type) {
if ("add".equals(type)) {
return new AddOperation();
}
if ("sub".equals(type)) {
return new SubOperation();
}
throw new RuntimeException("Unsupported operation");
}
}
客户端调用方式:
java复制Operation operation = OperationFactory.createOperation("add");
double result = operation.calculate(10, 5);
注意:工厂方法通常设计为静态方法,这样客户端可以直接调用而无需实例化工厂类。但这也意味着工厂类难以扩展,这是简单工厂的一个局限。
2.3 优缺点与适用场景
简单工厂模式的优点很明显:
- 客户端与具体实现解耦
- 对象创建逻辑集中管理
- 代码更清晰,减少重复的创建代码
但它也有明显的缺点:
- 违反开闭原则 - 新增产品类型需要修改工厂类
- 工厂类职责过重 - 所有创建逻辑集中在一个类中
- 难以扩展 - 静态工厂方法无法通过继承来扩展
适用场景:
- 对象创建逻辑相对简单
- 产品类型不会频繁变化
- 不需要运行时动态切换产品类型
典型应用包括:
- 日志记录器的创建
- 数据库连接池的获取
- 各种工具类的实例化
3. 策略模式深度剖析
3.1 设计理念与架构
策略模式的核心在于定义一系列算法,并使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端。举个例子,就像你选择手机支付方式:支付宝、微信支付或银行卡支付,你可以随时切换,而收银台不需要做任何修改。
策略模式包含三个主要角色:
- 策略接口(Strategy)
- 具体策略实现(AddStrategy等)
- 上下文类(Context)
上下文类持有一个策略引用,并将工作委托给当前策略。客户端负责选择具体策略并设置到上下文中。
3.2 完整代码实现解析
策略接口定义:
java复制public interface Strategy {
double calculate(double a, double b);
}
具体策略实现,如加法策略:
java复制public class AddStrategy implements Strategy {
@Override
public double calculate(double a, double b) {
return a + b;
}
}
上下文类的实现:
java复制public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public double execute(double a, double b) {
return strategy.calculate(a, b);
}
// 允许动态切换策略
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}
客户端调用示例:
java复制Strategy strategy = new AddStrategy();
Context context = new Context(strategy);
double result = context.execute(10, 5);
提示:上下文类通常提供设置新策略的方法,这使得策略可以在运行时动态切换,这是策略模式的一大优势。
3.3 模式优势与典型应用
策略模式的优点:
- 符合开闭原则 - 新增策略无需修改现有代码
- 避免多重条件语句 - 用组合代替条件判断
- 算法可以自由切换 - 运行时动态改变行为
- 算法复用 - 同一策略可用于不同上下文
缺点:
- 客户端必须了解不同策略的区别
- 策略类数量可能很多
- 通信开销 - 策略与上下文之间需要交互
典型应用场景:
- 支付方式选择
- 排序算法切换
- 折扣计算策略
- 数据压缩算法
- 路径规划算法
4. 两种模式的核心区别
4.1 设计目的对比
简单工厂模式和策略模式虽然都涉及"选择",但它们的选择对象和目的完全不同:
| 对比维度 | 简单工厂模式 | 策略模式 |
|---|---|---|
| 核心目的 | 封装对象创建过程 | 封装算法或行为 |
| 关注点 | 创建哪个对象 | 使用哪种行为 |
| 选择时机 | 编译时/创建时确定 | 运行时可以动态改变 |
| 客户端参与度 | 客户端只需指定类型 | 客户端需要选择具体策略 |
| 扩展性 | 新增类型需修改工厂类 | 新增策略不影响现有代码 |
4.2 代码结构差异
简单工厂的典型结构:
code复制客户端 -> 工厂 -> 具体产品
策略模式的典型结构:
code复制客户端 -> 上下文 -> 当前策略
↳ 可选策略1
↳ 可选策略2
在简单工厂中,选择是一次性的,发生在对象创建时。而在策略模式中,选择是动态的,可以在任何时候改变策略。
4.3 设计原则遵循度
从设计原则的角度看:
- 简单工厂通常违反开闭原则,因为新增产品类型需要修改工厂类
- 策略模式完美符合开闭原则,新增策略不影响现有代码
- 两者都符合单一职责原则,将创建或算法逻辑分离
- 两者都使用面向接口编程,而不是针对实现编程
5. 实战应用场景分析
5.1 支付系统设计案例
假设我们要设计一个支付系统,支持多种支付方式。如果用简单工厂:
java复制public class PaymentFactory {
public static Payment create(String type) {
if ("alipay".equals(type)) {
return new Alipay();
}
if ("wechat".equals(type)) {
return new WechatPay();
}
throw new RuntimeException("Unsupported payment");
}
}
客户端使用:
java复制Payment payment = PaymentFactory.create("alipay");
payment.pay(100);
如果用策略模式:
java复制public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(double amount) {
strategy.pay(amount);
}
}
客户端使用:
java复制PaymentStrategy strategy = new AlipayStrategy();
PaymentContext context = new PaymentContext(strategy);
context.executePayment(100);
策略模式的优势在于:
- 可以在支付过程中切换策略(比如首选支付失败时)
- 新增支付方式不需要修改现有代码
- 更容易进行单元测试
5.2 电商促销场景
电商平台经常需要实现不同的促销策略:满减、折扣、赠品等。用策略模式实现:
java复制public interface PromotionStrategy {
double applyPromotion(double originalPrice);
}
public class DiscountStrategy implements PromotionStrategy {
private double discountRate;
public DiscountStrategy(double discountRate) {
this.discountRate = discountRate;
}
@Override
public double applyPromotion(double originalPrice) {
return originalPrice * discountRate;
}
}
public class PromotionContext {
private PromotionStrategy strategy;
public void setStrategy(PromotionStrategy strategy) {
this.strategy = strategy;
}
public double executePromotion(double price) {
return strategy.applyPromotion(price);
}
}
这种设计允许在运行时根据促销活动动态切换策略,非常灵活。
6. 模式选择指南与最佳实践
6.1 何时选择简单工厂
选择简单工厂模式的场景:
- 对象创建逻辑相对复杂,需要集中管理
- 产品类型较少且不经常变化
- 客户端不需要关心具体实现类
- 不需要运行时动态切换对象类型
简单工厂特别适合:
- 配置驱动的对象创建
- 插件系统的初始化
- 需要隐藏创建细节的场景
6.2 何时选择策略模式
选择策略模式的场景:
- 需要动态切换算法或行为
- 有多种相似的算法,仅在具体实现上不同
- 需要避免暴露复杂的算法细节
- 算法可能经常变化或扩展
策略模式特别适合:
- 用户可配置的行为
- 需要灵活替换算法的场景
- 复杂的业务规则处理
6.3 组合使用模式
在实际项目中,经常会将简单工厂和策略模式组合使用。例如:
java复制public class StrategyFactory {
public static Strategy create(String type) {
if ("add".equals(type)) {
return new AddStrategy();
}
if ("sub".equals(type)) {
return new SubStrategy();
}
throw new RuntimeException("Unsupported strategy");
}
}
// 客户端使用
Strategy strategy = StrategyFactory.create("add");
Context context = new Context(strategy);
这种组合既利用了工厂封装创建逻辑的优点,又保留了策略模式动态行为的灵活性。
7. 常见误区与避坑指南
7.1 典型误用场景
-
滥用简单工厂:
- 当对象类型经常变化时仍使用简单工厂
- 将不相关的对象创建逻辑放在同一个工厂中
- 工厂方法过于复杂,包含大量条件判断
-
策略模式使用不当:
- 策略实现类之间有过多的重复代码
- 上下文类过于庞大,承担了过多职责
- 客户端需要了解太多策略细节
7.2 性能考量
-
简单工厂的性能影响:
- 工厂方法中的条件判断在频繁调用时可能影响性能
- 可以考虑使用Map缓存产品实例(如果是无状态对象)
-
策略模式的性能优化:
- 避免在频繁调用的方法中动态切换策略
- 对于简单的策略,可以考虑使用lambda表达式实现
7.3 测试注意事项
-
简单工厂的测试:
- 重点测试工厂方法的各种分支
- 模拟不同类型参数验证工厂行为
- 测试异常情况的处理
-
策略模式的测试:
- 为每个策略实现编写单元测试
- 测试上下文类与策略的交互
- 验证策略切换的正确性
在实际项目中,我经常看到开发者过度设计,在不必要的场景使用策略模式,导致代码复杂度增加。记住:不是所有需要选择的地方都需要策略模式,只有当行为需要动态切换时才考虑使用它。