策略模式
它和模板方法模式是亲兄弟,解决的都是“算法封装”的问题,但手段截然不同:模板方法用的是继承(比较死板),策略模式用的是组合(非常灵活)。
1. 策略模式 (Strategy Pattern)
场景设定:僵尸游戏
假设你正在写一个打僵尸的游戏。玩家手中的武器是可以随时切换的:
- 用匕首时,近距离刺杀。
- 用AK47时,远距离扫射。
- 用手雷时,范围爆炸。
如果你在 Player 类里写一堆 if (weapon == KNIFE) { ... } else if (weapon == GUN) { ... },代码会非常丑陋且难以维护。
代码示例
#include <iostream>
#include <memory> // 使用智能指针管理内存
using namespace std;
// ==========================================
// 1. 抽象策略 (Strategy Interface)
// 稳定点:无论什么武器,攻击这个动作的接口是统一的
// ==========================================
class IWeapon {
public:
virtual ~IWeapon() {}
// 纯虚函数:具体怎么攻击,由子类决定
virtual void UseWeapon() = 0;
};
// ==========================================
// 2. 具体策略 (Concrete Strategies)
// 变化点:各种不同的攻击算法
// ==========================================
class Knife : public IWeapon {
public:
void UseWeapon() override {
cout << " [攻击] 挥舞匕首 -> 造成 10 点近战伤害" << endl;
}
};
class AK47 : public IWeapon {
public:
void UseWeapon() override {
cout << " [攻击] AK47 突突突 -> 造成 50 点远程伤害" << endl;
}
};
class Grenade : public IWeapon {
public:
void UseWeapon() override {
cout << " [攻击] 扔出手雷 -> BOOM! 造成 100 点范围伤害" << endl;
}
};
// ==========================================
// 3. 上下文环境 (Context)
// 稳定点:角色使用武器的流程
// ==========================================
class Character {
private:
// 【核心】:使用组合 (Composition) 持有策略接口
// 这里使用 unique_ptr 自动管理内存,也可以用原始指针
IWeapon* weapon;
public:
Character() : weapon(nullptr) {}
// 【动态切换策略】
// 这是策略模式比模板方法强大的地方:运行时换脑子
void SetWeapon(IWeapon* newWeapon) {
this->weapon = newWeapon;
}
// 执行策略
void Attack() {
if (weapon == nullptr) {
cout << " [攻击] 赤手空拳 -> 造成 1 点伤害" << endl;
} else {
// 多态调用:委托给具体的策略对象
weapon->UseWeapon();
}
}
};
// ==========================================
// 客户端代码
// ==========================================
int main() {
Character player;
// 1. 初始状态
cout << "=== 遭遇普通僵尸 ===" << endl;
Knife* knife = new Knife();
player.SetWeapon(knife); // 装备匕首
player.Attack();
// 2. 动态切换算法
cout << "\n=== 遭遇尸潮 ===" << endl;
AK47* gun = new AK47();
player.SetWeapon(gun); // 换枪
player.Attack();
// 3. 再次切换
cout << "\n=== 遭遇 BOSS ===" << endl;
Grenade* boom = new Grenade();
player.SetWeapon(boom); // 换手雷
player.Attack();
// 清理内存
delete knife;
delete gun;
delete boom;
return 0;
}
1. 定义
- 原文: 定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
- 解读:
- “一系列算法”:匕首、枪、手雷都是“攻击算法”。
- “相互替换”:因为它们都实现了
IWeapon接口,对于Character来说,它们是一样的,随时可以换。
2. 解决的问题 (Stable vs. Changing)
- 稳定点 (Stable):客户的使用请求
Character类中调用Attack()的时机和逻辑是稳定的。
- 变化点 (Changing):具体的算法实现
- 具体的伤害计算、攻击特效是变化的。
3. 策略模式 vs 模板方法模式 (核心对比)
这是面试必考题,两者都在做“算法抽象”,区别在哪里?
| 特性 | 模板方法 (Template Method) | 策略模式 (Strategy Pattern) |
|---|---|---|
| 复用方式 | 继承 (Class Inheritance) | 组合 (Object Composition) |
| 绑定时间 | 编译期 (Compile-time) | 运行期 (Runtime) |
| 灵活性 | 低(父类定死框架,无法中途改) | 高 (随时调用 SetWeapon 切换) |
| 关系 | "Is-A" (DolphinShow 是 ZooShow) | "Has-A" (Character 有一把 Weapon) |
| 适用场景 | 算法骨架固定,只有个别步骤变 | 整个算法逻辑经常整体替换 |
一句话总结: 如果你需要在程序运行过程中切换算法(比如打游戏换枪、商场促销切优惠券),必须用策略模式。
4. 符合哪些设计原则?
- A. 开闭原则 (OCP)
- 对扩展开放: 想加一个“激光枪”?新建
LaserGun类继承IWeapon即可。 - 对修改关闭:
Character类的代码完全不用动。
- 对扩展开放: 想加一个“激光枪”?新建
- B. 合成复用原则 (Composition over Inheritance)
- 这是策略模式的灵魂。它没有让
Character去继承Weapon,而是让Character持有Weapon。 - 这避免了类爆炸(不需要
KnifeCharacter,GunCharacter这种继承类)。
- 这是策略模式的灵魂。它没有让
5. C++ 现代扩展 (Function 对象)
在现代 C++ (C++11 及以后) 中,如果策略很简单,我们甚至不需要写这么多类。可以使用 std::function 配合 Lambda 表达式来实现轻量级策略模式。
#include <functional>
class Character {
// 直接持有一个函数对象,而不是指针
std::function<void()> attackStrategy;
public:
void SetStrategy(std::function<void()> strategy) {
attackStrategy = strategy;
}
void Attack() {
if(attackStrategy) attackStrategy();
}
};
int main() {
Character p;
// 使用 Lambda 表达式直接定义策略,不需要写 Knife 类了
p.SetStrategy([](){ cout << "挥刀!" << endl; });
p.Attack();
p.SetStrategy([](){ cout << "开枪!" << endl; });
p.Attack();
}
总结
策略模式完美诠释了“把变化关进笼子”的思想:
- 笼子:
IWeapon接口。 - 猫: 各种具体的武器算法。
- 主人:
Character,手里拿着笼子的钥匙(指针),想装哪只猫就装哪只猫。
浙公网安备 33010602011771号