当前位置: 首页 > article >正文

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_BXDItemStrategy_DHDItemStrategy_SHD类。

策略模式结构

在这里插入图片描述

引人策略设计模式的定义:定义一系列算法类(策略类),将每个算法封装起来,让它们可以相互替换。换句话说,策略模式通常把一系列算法封装到一系列具体策略类中作为抽象策略类的子类,然后根据实际需要使用这些子类。

假如你需要前往机场。 你可以选择乘坐公共汽车、 预约出租车或骑自行车。 这些就是你的出行策略。 你可以根据预算或时间等因素来选择其中一种策略。


三、总结

策略模式中的若干个策略对象相互之间是完全独立的, 它们不知道其他对象的存在。当我们想使用对象中各种不同的算法变体,并希望能够在运行的时候切换这些算法时,可以选择使用策略模式来处理这个问题。

以往利用增加新的f条件分支来支持新算法的方式违背了开闭原则,引人策略模式后,通过增加新的策略子类实现了对开闭原则的完全支持,也就是以扩展的方式支持未来的变化。所以,如果读者今后在编写代码时遇到有多个if条件分支或者switch分支的语句,并且这些分支并不稳定,会经常改动时,则率先考虑能否通过引入策略模式加以解决,所以很多情况下,策略模式是if或者switch条件分支的取代者。

装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。

模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。

装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。

模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。

同时,状态模式可被视为策略模式的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。


http://www.kler.cn/a/413314.html

相关文章:

  • Group Convolution(分组卷积)
  • [护网杯 2018]easy_tornado
  • Easy Excel 通过【自定义批注拦截器】实现导出的【批注】功能
  • Paddle Inference部署推理(三)
  • DB2数据库
  • ctfshow
  • python控制鼠标,键盘,adb
  • 使用SQL按每小时统计数据的方法
  • C#设计模式——抽象工厂模式(重点)
  • Python使用ffmpeg进行本地视频拉流,并使用训练模型识别人脸,并将识别后的模型推流源码
  • frida_hook_libart(简单解释)
  • 介绍SSD硬盘
  • C#里怎么样使用LINQ的let关键字实现查询?
  • 基于Qt实现的自定义树结构容器:设计与应用
  • 摄像头原始数据读取——ffmpeg(av_read_frame)
  • springboot学习-分页/排序/多表查询的例子
  • 如何在CodeIgniter中添加或加载模型
  • 2024年11月24日Github流行趋势
  • 道格拉斯-普克算法(Douglas-Peucker algorithm)
  • Android Audio实战——音频多声道基础适配(七)
  • windows 服务器角色
  • 使用guzzlehttp异步多进程实现爬虫业务
  • 【SpringCloud详细教程】-04-服务容错--Sentinel
  • Fiddler导出JMeter脚本插件原理
  • 安卓 获取 喇叭 听筒 音频输出流 AudioPlaybackCapture API 可以捕获音频输出流
  • 如何提升爬虫的效率和稳定性?