2025-06-26 15:32:07 +08:00
|
|
|
|
### 一、策略模式 (Strategy)
|
2025-03-24 16:04:56 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
**核心思想**:
|
|
|
|
|
把可互换的算法/行为抽成独立策略类,运行时由“上下文”对象选择合适的策略;对调用方来说,只关心统一接口,而非具体实现。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌───────────────┐
|
|
|
|
|
│ Client │
|
|
|
|
|
└─────▲─────────┘
|
|
|
|
|
│ has-a
|
|
|
|
|
┌─────┴─────────┐ implements
|
|
|
|
|
│ Context │────────────┐ ┌──────────────┐
|
|
|
|
|
│ (使用者) │ strategy └─▶│ Strategy A │
|
|
|
|
|
└───────────────┘ ├──────────────┤
|
|
|
|
|
│ Strategy B │
|
|
|
|
|
└──────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Demo:支付策略(Java)
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
// 1. 抽象策略
|
|
|
|
|
public interface PayStrategy {
|
|
|
|
|
void pay(int cents);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 具体策略
|
|
|
|
|
public class AliPay implements PayStrategy {
|
|
|
|
|
public void pay(int cents) { System.out.println("Alipay ¥" + cents / 100.0); }
|
|
|
|
|
}
|
|
|
|
|
public class WxPay implements PayStrategy {
|
|
|
|
|
public void pay(int cents) { System.out.println("WeChat Pay ¥" + cents / 100.0); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 上下文
|
|
|
|
|
public class PaymentService {
|
|
|
|
|
private final PayStrategy strategy;
|
|
|
|
|
public PaymentService(PayStrategy strategy) { this.strategy = strategy; }
|
|
|
|
|
public void checkout(int cents) { strategy.pay(cents); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 运行时选择策略
|
|
|
|
|
public class Demo {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
PaymentService ps1 = new PaymentService(new AliPay());
|
|
|
|
|
ps1.checkout(2599); // Alipay ¥25.99
|
|
|
|
|
|
|
|
|
|
PaymentService ps2 = new PaymentService(new WxPay());
|
|
|
|
|
ps2.checkout(4999); // WeChat Pay ¥49.99
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**要点**
|
|
|
|
|
|
|
|
|
|
- **开放封闭**:新增 PayPal 只需实现 `PayStrategy`,无须改 `PaymentService`。
|
|
|
|
|
- **运行期切换**:可根据配置、用户偏好等动态注入不同策略。
|
2025-04-13 14:09:55 +08:00
|
|
|
|
|
2025-04-29 18:12:50 +08:00
|
|
|
|
------
|
2025-04-13 14:09:55 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
### 二、模板方法模式 (Template Method)
|
|
|
|
|
|
|
|
|
|
**核心思想**:
|
|
|
|
|
在抽象父类中定义**算法骨架**(固定执行顺序),把某些可变步骤留给子类重写;调用方只用模板方法,保证流程一致。
|
2025-04-13 14:09:55 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
```
|
|
|
|
|
Client ───▶ AbstractClass
|
|
|
|
|
├─ templateMethod() ←—— 固定流程
|
|
|
|
|
│ step1()
|
|
|
|
|
│ step2() ←—— 抽象,可变
|
|
|
|
|
│ step3()
|
|
|
|
|
└─ hookMethod() ←—— 可选覆盖
|
|
|
|
|
▲
|
|
|
|
|
│ extends
|
|
|
|
|
┌──────────┴──────────┐
|
|
|
|
|
│ ConcreteClassA/B… │
|
|
|
|
|
```
|
2025-04-13 14:09:55 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
#### Demo:弹窗加载流程(Java)
|
2025-04-13 14:09:55 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
```java
|
|
|
|
|
// 1. 抽象模板
|
|
|
|
|
public abstract class AbstractDialog {
|
2025-04-13 14:09:55 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
// 模板方法:固定调用顺序,设为 final 防止子类改流程
|
|
|
|
|
public final void show() {
|
|
|
|
|
initLayout();
|
|
|
|
|
bindEvent();
|
|
|
|
|
beforeDisplay(); // 钩子,可选
|
|
|
|
|
display();
|
|
|
|
|
afterDisplay(); // 钩子,可选
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 具体公共步骤
|
|
|
|
|
private void initLayout() {
|
|
|
|
|
System.out.println("加载通用布局文件");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 需要子类实现的抽象步骤
|
|
|
|
|
protected abstract void bindEvent();
|
|
|
|
|
|
|
|
|
|
// 钩子方法,默认空实现
|
|
|
|
|
protected void beforeDisplay() {}
|
|
|
|
|
protected void afterDisplay() {}
|
|
|
|
|
|
|
|
|
|
private void display() {
|
|
|
|
|
System.out.println("弹出对话框");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 子类:登录对话框
|
|
|
|
|
public class LoginDialog extends AbstractDialog {
|
|
|
|
|
@Override
|
|
|
|
|
protected void bindEvent() {
|
|
|
|
|
System.out.println("绑定登录按钮事件");
|
|
|
|
|
}
|
|
|
|
|
@Override
|
|
|
|
|
protected void afterDisplay() {
|
|
|
|
|
System.out.println("focus 到用户名输入框");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 调用
|
|
|
|
|
public class Demo {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
AbstractDialog dialog = new LoginDialog();
|
|
|
|
|
dialog.show();
|
|
|
|
|
/* 输出:
|
|
|
|
|
加载通用布局文件
|
|
|
|
|
绑定登录按钮事件
|
|
|
|
|
弹出对话框
|
|
|
|
|
focus 到用户名输入框
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**要点**
|
|
|
|
|
|
|
|
|
|
- **复用公共流程**:`initLayout()`、`display()` 写一次即可。
|
|
|
|
|
- **限制流程顺序**:`show()` 定为 `final`,防止子类乱改步骤。
|
|
|
|
|
- **钩子方法**:子类可选择性覆盖(如 `beforeDisplay`)。
|
2025-04-29 18:12:50 +08:00
|
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
### 关键区别 & 组合用法
|
|
|
|
|
|
|
|
|
|
| | **策略模式** | **模板方法模式** |
|
|
|
|
|
| ---------------- | ---------------------------------- | ---------------------------------------- |
|
|
|
|
|
| **目的** | **横向**扩展——允许算法**并列互换** | **纵向**复用——抽取算法**骨架**,固定顺序 |
|
|
|
|
|
| **实现方式** | 组合 + 接口 | 继承 + 抽象父类 |
|
|
|
|
|
| **行为选择时机** | 运行时由外部注入 | 编译期由继承确定 |
|
|
|
|
|
| **常组合** | 与 **工厂模式**配合选择策略 | 与 **钩子方法**、**回调**一起用 |
|
|
|
|
|
|
|
|
|
|
在实际项目中,两者经常**组合**:
|
2025-05-20 19:32:40 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
> 折扣计算 **Strategy** → 公共过滤 & 日志 **Template Method** → Spring 容器负责策略注册/发现。
|
2025-04-29 18:12:50 +08:00
|
|
|
|
|
2025-06-26 15:32:07 +08:00
|
|
|
|
这样即可同时获得“纵向流程复用”+“横向算法可插拔”的双重优势。
|