场景设定
我们需要设计一个 Hero(英雄)类,他有一个 Attack(攻击)的行为。
英雄可以使用'剑'攻击,也可以用'火球'攻击,甚至可以'空手'攻击。
1. 传统的做法(虚函数 + 继承)
在传统 OOP 中,如果不使用策略模式,你可能会觉得:既然攻击方式不同,那就派生出不同的子类吧。
// 基类
class Hero {
public:
virtual void Attack() {
cout << "空手攻击!" << endl;
}
};
// 派生类:剑士
class Swordsman : public Hero {
public:
void Attack() override {
cout << "挥剑斩击!" << endl;
}
};
// 派生类:法师
class Mage : public Hero {
public:
void Attack() override {
cout << "发射火球!" << endl;
}
};
这种写法的痛点:
- 死板:一旦你创建了一个
Swordsman对象,他就永远只能挥剑。如果他捡到一根魔杖想变成法师怎么办?你没法把一个Swordsman对象直接变成Mage对象。 - 类爆炸:如果你有 100 种武器,你难道要写 100 个
Hero的子类吗?
2. 新写法(std::function + 策略注入)
现在我们用你提到的方法。Hero 不再有子类,它只是一个'容器',它的 Attack 行为由一个 std::function 变量决定。
我们可以把这个变量理解为英雄装备的'技能卡槽'。
A. 定义 Hero 类
#include <iostream>
#include <functional>
class Hero; // 前置声明
// 定义接口类型:这是一个'函数签名',它接受一个 Hero 引用,没有返回值
using AttackStrategy = std::function<void(const Hero&)>;
// 默认的攻击方式(普通函数)
void DefaultPunch(const Hero& h) {
std::cout << "英雄使用了:普通拳击 (默认)" << std::endl;
}
class Hero {
public:
// 构造函数:默认装备'普通拳击'技能卡
Hero(AttackStrategy strategy = DefaultPunch) : attack_skill(strategy) {}
// 公开的非虚函数接口
void DoAttack() {
std::cout << "[准备动作] 英雄深吸一口气..." << std::endl;
// 核心:调用当前装备的技能卡
attack_skill(*this);
std::cout << "[结束动作] 攻击完成。" << std::endl;
}
// 关键点:我们可以随时更换技能卡!
void ChangeWeapon(AttackStrategy new_strategy) {
attack_skill = new_strategy;
}
private:
AttackStrategy attack_skill; // 这里保存了具体的'怎么打'
};
B. 三种不同的实现(举例说明)
现在,可以通过给 attack_skill 赋值不同的内容,让同一个 Hero 对象表现出完全不同的样子。
例子 1:普通函数(最简单的实现)
// 一个普通的全局函数
void UseSword(const Hero& h) {
std::cout << ">>> EXCALIBUR! (使用圣剑攻击)" << std::endl;
}
// 使用:
Hero h1(UseSword);
h1.DoAttack();
// 输出:
// [准备动作] ...
// >>> EXCALIBUR! ...
例子 2:Lambda 表达式(最方便的实现)
有时候我们不想专门写个函数,只想临时定义一个招式,比如'扔石头'。
// 使用 Lambda 直接定义逻辑
Hero h2([](const Hero& h) {
std::cout << ">>> 扔出了一块石头!(Lambda 实现)" << std::endl;
});
h2.DoAttack();
例子 3:成员函数 + std::bind(最复杂的实现,也是你困惑的地方)
假设我们有一个复杂的 MagicBook(魔法书)类,里面存了很多法术。这些法术是 MagicBook 的成员函数,而不是全局函数。
class MagicBook {
public:
void CastFireball(const Hero& h) {
std::cout << ">>> 这里的魔法书:发射大火球!" << std::endl;
}
void CastIceStorm(const Hero& h) {
std::cout << ">>> 这里的魔法书:召唤暴风雪!" << std::endl;
}
};
现在问题来了:MagicBook::CastFireball 也是一个函数,但它的参数其实有两个:(MagicBook* this, const Hero& h)。
而我们的 std::function 只接受一个参数 (const Hero& h)。签名不匹配!
这就好比插头(函数)是三孔的,但插座(std::function)是两孔的。
std::bind 就是那个转接头。它把 MagicBook 的对象实例(this)直接'焊死'在函数上,让它对外看起来只剩下一个参数。
int main() {
MagicBook myBook; // 我有一本魔法书对象
// 创建一个英雄
Hero h3;
// 1. 英雄想用魔法书里的火球术
// 这里的 bind 意思:把 myBook 绑定到 CastFireball 的第一个参数(this) 上
// std::placeholders::_1 表示:Hero 参数留空,等调用的时候再填
h3.ChangeWeapon(std::bind(&MagicBook::CastFireball, &myBook, std::placeholders::_1));
h3.DoAttack(); // 输出:发射大火球!
// 2. 英雄想换成冰风暴
h3.ChangeWeapon(std::bind(&MagicBook::CastIceStorm, &myBook, std::placeholders::_1));
h3.DoAttack(); // 输出:召唤暴风雪!
return 0;
}
总结对比
| 特性 | 传统虚函数写法 | std::function 写法 (策略模式) |
|---|---|---|
| 实现方式 | class Swordsman : public Hero | h.ChangeWeapon(UseSword) |
| 切换行为 | 不可能 (你是剑士就一辈子是剑士) | 极易 (运行时随时调用 ChangeWeapon) |
| 扩展性 | 需新增子类 | 无需新增类,仅修改策略 |

