C++设计模式行为模式———策略模式
文章目录
- 一、引言
- 二、策略模式
- 三、总结
一、引言
策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。与模板方法模式类似,都是以扩展的方式来支持未来的变化。
策略模式需要我们定义一系列的算法,并且将每种算法都放入到独立的类中,在实际操作的时候使这些算法对象可以相互替换。
二、策略模式
使用闯关打斗游戏为例,当主角走到某个特定的场景位置或者击杀某个大型怪物后,这些道具就会出现,主角通过走到该道具上就可以实现为自身补充生命值的目的。前期主要规划了3个道具(药品):
- 补血丹可以补充200点生命值。
- 大还丹可以补充300点生命值。
- 守护丹可以补充500点生命值。
首先创建出几种战士类型,都继承于Fighter
。
class Fighter {
public:
Fighter(int life, int magic, int attack)
:m_life(life) {}
virtual ~Fighter() {}
void setLife(int life) { m_life = life; }
int getLife() { return m_life; }
private:
int m_life;//生命值
};
//"战士"类,父类为Fighter
class F_Warrior :public Fighter {
public:
F_Warrior(int life, int magic, int attack) :Fighter(life, magic, attack) {}
};
//"法师"类,父类为Fighter
class F_Mage :public Fighter {
public:
F_Mage(int life, int magic, int attack) :Fighter(life, magic, attack) {}
};
我们设置一个枚举类型来表示三种加血道具,同时给Fighter增加补血函数:
enum class ItemAddlife
{
LF_BXD, LF_DHD, LF_SHD
};
void Fighter::UseItem(ItemAddlife type)
{
switch (type)
{
case ItemAddlife::LF_BXD:
m_life += 200;
break;
case ItemAddlife::LF_DHD:
m_life += 300;
break;
case ItemAddlife::LF_SHD:
m_life += 500;
break;
default:
break;
}
}
此时如果要加血的话,我们的战士子类或法师子类直接调用useItem
函数即可,但是要增加新的枚举类型,也要在UseItem
中的switch语句中增加判断条件,这不符合开闭原则,而且一旦条件特别多,对程序的运行效率和可维护性会造成影响。
下面使用策略模式对上述代码进行改进,在策略模式中,可以把UseItem
成员函数中的每个条件分支中的代码(也称“算法”)写到一个个类中,那么每个封装了算法的类就可以称为一种策略(类不仅可以表示一种存在于真实世界的东西,也可以表示一种不存在于真实世界的东西),当然,应该为这些策略抽象出一个统一的父类以便实现多态:
class ItemStrategy
{
public:
virtual void UseItem(Fighter* mainobj) = 0;
virtual ~ItemStrategy() {}
};
//补血丹策略类
class ItemStrategy_BxD :public ItemStrategy {
public:
virtual void UseItem(Fighter* mainobj) override
{
mainobj->setLife(mainobj->getLife() + 200);//补充200点生命值
}
};
//大还丹策略类
class ItemStrategy_DHD :public ItemStrategy {
public:
virtual void UseItem(Fighter* mainobj) {
mainobj->setLife(mainobj->getLife() + 300);//补充300点生命值
}
};
//守护丹策略类
class ItemStrategy_SHD :public ItemStrategy
{
public:
virtual void UseItem(Fighter* mainobj) {
mainobj->setLife(mainobj->getLife() + 500);//补充500点生命值
}
};
从上面的代码中可以看到,UseItem
成员函数直接使用了Fighter*
作为形参,意图是把主角所有必要的信息都传递到策略类中来,让策略类中的UseItem
成员函数在需要时可以随时回调Fighter
中的各种成员函数。下面我们修改Fighter
:
class Fighter {
public:
Fighter(int life)
:m_life(life) {}
void UseItem(ItemStrategy* type);
void setLife(int life) { m_life = life; }
int getLife() { return m_life; }
void setItemStrategy(ItemStrategy* star) {
type = star;
}
virtual ~Fighter() {}
private:
ItemStrategy* type;
int m_life;//生命值
};
void Fighter::UseItem(ItemStrategy* type)
{
type->UseItem(this);
}
如上,将算法(使用道具增加生命值这件事)本身独立到ItemStrategy
的各个子类中,而不在Fighter
类中实现。当增加新的道具时,只需要增加一个新的策略子类即可,这样就符合开闭原则了。
Fighter
类与ItemStrategy
类相互作用实现指定的算法,当算法被调用时,Fighter
将算法需要的所有数据(这里其实是Fighter类对象自身)传递给ItemStrategy
,当然如果算法需要的数据比较少,则可以仅仅传递必需的数据(而不必将Fighter
类对象本身传递给算法)。
策略模式一般有三种角色:
- 上下文类(Context)是使用算法的角色,该类中维持着一个对抽象策略类的指针或引用。这里指
Fighter
类。 - 抽象策略类(Strategy):定义义所支持的算法的公共接口,是所有策略类的父类。这里指
ItemStrategy
类。 - 具体策略类(ConcreteStrategy):抽象策略类的子类,实现抽象策略类中声明的接口。这里指
ItemStrategy_BXD
、ItemStrategy_DHD
、ItemStrategy_SHD
类。
策略模式结构
引人策略设计模式的定义:定义一系列算法类(策略类),将每个算法封装起来,让它们可以相互替换。换句话说,策略模式通常把一系列算法封装到一系列具体策略类中作为抽象策略类的子类,然后根据实际需要使用这些子类。
假如你需要前往机场。 你可以选择乘坐公共汽车、 预约出租车或骑自行车。 这些就是你的出行策略。 你可以根据预算或时间等因素来选择其中一种策略。
三、总结
策略模式中的若干个策略对象相互之间是完全独立的, 它们不知道其他对象的存在。当我们想使用对象中各种不同的算法变体,并希望能够在运行的时候切换这些算法时,可以选择使用策略模式来处理这个问题。
以往利用增加新的f条件分支来支持新算法的方式违背了开闭原则,引人策略模式后,通过增加新的策略子类实现了对开闭原则的完全支持,也就是以扩展的方式支持未来的变化。所以,如果读者今后在编写代码时遇到有多个if条件分支或者switch分支的语句,并且这些分支并不稳定,会经常改动时,则率先考虑能否通过引入策略模式加以解决,所以很多情况下,策略模式是if或者switch条件分支的取代者。
装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。
模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。
装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。
模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。
同时,状态模式可被视为策略模式的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。