策略模式和工厂模式的区别?
结论先行:工厂管"造",策略管"做"。工厂模式解决"对象怎么创建、由谁创建",创建完就退场;策略模式解决"同一件事有多个算法,运行时怎么替换",对象要在整个调用期被使用。两者可以叠加——用工厂造策略,用策略消分支,这是工程里最常见的组合。
1. 逐项对比
| 对比项 | 工厂模式 | 策略模式 |
|---|---|---|
| 一句话意图 | 把创建逻辑集中,调用方不 new | 把算法族封装,运行时可替换 |
| 解决的问题 | 创建的耦合 | 行为的耦合 |
| 客户端关心什么 | 只要产品,不关心实现类 | 知道有哪些策略,或由上下文选择 |
| 返回/产出 | 一个对象 | 一次行为结果 |
| 生命周期 | 创建后交给调用方 | 上下文持有,持续参与调用 |
| 变化维度 | 产品种类 | 算法种类 |
| 典型例子 | BeanFactory、PayChannelFactory | 计价规则、路由策略、压缩算法 |
2. 结构差异:一条是"生产链",一条是"调用链"
工厂模式:造完就退场
┌────────┐ create() ┌──────────┐ ┌──────────┐
│ Client │───────────►│ Factory │─►│ Product │
└────────┘ └──────────┘ └──────────┘
策略模式:每次调用都经过上下文
┌────────┐ execute() ┌──────────┐ ┌──────────┐
│ Client │────────────►│ Context │─►│ Strategy │◄── 运行时可替换
└────────┘ └──────────┘ └──────────┘
看图的差别:工厂的箭头到产品就结束了;策略的箭头在"上下文"和"算法"之间来回。
3. 只有工厂:解决创建
interface Notifier { void send(String msg); }
class SmsNotifier implements Notifier {
public void send(String msg) { System.out.println("短信: " + msg); }
}
class MailNotifier implements Notifier {
public void send(String msg) { System.out.println("邮件: " + msg); }
}
class NotifierFactory {
static Notifier create(String type) {
switch (type) {
case "sms": return new SmsNotifier();
case "mail": return new MailNotifier();
default: throw new IllegalArgumentException("未知渠道: " + type);
}
}
}
// 调用方:拿到对象后自己调
Notifier n = NotifierFactory.create(config.getChannel());
n.send("订单已发货");
这里没有"算法可切换"的诉求,只是不想让调用方写 new,这就是纯工厂。
4. 工厂 + 策略:各管一段
interface DiscountStrategy {
long apply(long amount);
String type();
}
class FullReduction implements DiscountStrategy {
public long apply(long amount) { return amount >= 100_00 ? amount - 10_00 : amount; }
public String type() { return "full_reduction"; }
}
class PercentOff implements DiscountStrategy {
public long apply(long amount) { return amount * 85 / 100; } // 85 折
public String type() { return "percent_off"; }
}
// 工厂:负责按类型产出策略,并集中注册
class DiscountFactory {
private static final Map<String, DiscountStrategy> REG = new HashMap<>();
static {
register(new FullReduction());
register(new PercentOff());
}
static void register(DiscountStrategy s) { REG.put(s.type(), s); }
static DiscountStrategy get(String type) {
DiscountStrategy s = REG.get(type);
if (s == null) throw new IllegalArgumentException("未知优惠类型: " + type);
return s;
}
}
// 上下文:持有策略,只负责编排
class PriceCalculator {
private final DiscountStrategy strategy;
PriceCalculator(String type) { this.strategy = DiscountFactory.get(type); }
long calc(long amount) { return strategy.apply(amount); }
}
分工很清晰:工厂解决"从字符串到对象"的映射,策略解决"不同算法的具体计算"。缺了工厂,策略的选取逻辑会散落在调用方;缺了策略,工厂造出来的对象只能靠 if 区分行为。
5. 什么场景只用其中一个
| 场景 | 该用什么 | 理由 |
|---|---|---|
| 产品有复杂构造过程(多参数、需校验) | 工厂 / 建造者 | 关注创建,不涉及行为切换 |
| 同一接口多种算法,运行时按配置切换 | 策略 | 关注行为,创建很简单 |
| 多渠道支付:构造客户端 + 不同签名算法 | 工厂 + 策略 | 创建与算法都要隔离 |
6. 常见追问
Q:Map<String, Strategy> 注册表算工厂还是策略?
它是工厂的变体(简单工厂 + 注册表),产出的对象恰好是策略。判断归属看职责:负责"给一个 key 换一个对象"就是工厂;负责"拿着对象算结果"才是策略。
Q:能不能用枚举 + 函数式接口替代这两个模式? 能覆盖大多数轻量场景:
enum Discount {
FULL_REDUCTION(a -> a >= 100_00 ? a - 10_00 : a),
PERCENT_OFF(a -> a * 85 / 100);
private final LongUnaryOperator op;
Discount(LongUnaryOperator op) { this.op = op; }
long apply(long a) { return op.applyAsLong(a); }
}
// Discount.valueOf(type).apply(amount);
枚举天然是"工厂 + 策略"的合体:valueOf 负责按名字取,常量自己带算法。缺点是算法复杂、需要依赖注入时不适合塞进枚举;算法需要访问 DAO 或 RPC 时,仍应回归独立策略类 + Spring 管理。
小结
- 工厂解决"创建",策略解决"行为替换";结构上工厂的链到产品终止,策略的链在上下文与算法间往返。
- 工程标配是工厂 + 策略:注册表消
switch,策略类放算法。 - 轻量场景可用枚举 + 函数式接口一步到位;判断归属只看职责,不看类图。