5.行为型模式

5.行为型模式

用来对类或对象怎样交互和怎样分配职责进行描述。

5.1模板方法模式

  定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

5.1.1模板方法模式中的角色和职责

AbstractClass(抽象类):在抽象类中定义了一系列基本操作,这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。

ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。

5.1.2模板方法模式的案例

// 08 模板方法模式.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
using namespace std;

class DrinkTemplate
{
public:
	//煮水
	virtual void BoildWater() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup() = 0;
	//加辅料
	virtual void AddSomething() = 0;

	//模板方法
	void Make()
	{
		//煮水
		BoildWater();
		//冲泡
		Brew();
		//倒入杯中
		PourInCup();
		//加辅料
		AddSomething();
	}
};

//冲泡咖啡
class Coffee : public DrinkTemplate
{
public:
	virtual void BoildWater()//煮水
	{
		cout << "煮山泉水..." << endl;
	}
	virtual void Brew()//冲泡
	{
		cout << "冲泡咖啡..." << endl;
	}
	virtual void PourInCup()//倒入杯中
	{
		cout << "咖啡倒入杯中..." << endl;
	}
	virtual void AddSomething()//加辅料
	{
		cout << "加糖、加牛奶、加点醋..." << endl;
	}
};

//冲泡茶水
class Tea : public DrinkTemplate
{
public:
	virtual void BoildWater()//煮水
	{
		cout << "煮自来水..." << endl;
	}
	virtual void Brew()//冲泡
	{
		cout << "冲泡铁观音..." << endl;
	}
	virtual void PourInCup()//倒入杯中
	{
		cout << "茶水倒入杯中..." << endl;
	}
	virtual void AddSomething()//加辅料
	{
		cout << "加点糖、加柠檬、加生姜..." << endl;
	}
};

void test01()
{
	Tea* tea = new Tea;
	tea->Make();

	cout << "-------------" << endl;

	Coffee* coffee = new Coffee;
	coffee->Make();
}
int main()
{
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

(1)模板方法模式核心定义

  1. 核心思想:定义一个操作中的算法框架(即固定步骤顺序),将算法中具体实现细节的步骤延迟到子类中完成。

  2. 关键特性

    • 子类可不改变算法的整体结构(步骤顺序固定);

    • 子类仅需重定义算法中的特定步骤(细节差异化实现);

    • 解决场景:多个业务场景的 “步骤流程一致,但每步细节不同”(如冲咖啡、煮茶)。

(2)典型案例:冲咖啡 vs 煮茶

  1. 场景分析​

冲咖啡和煮茶的步骤框架完全一致,但每步的具体细节不同,是模板方法模式的典型应用场景。

  1. 步骤对比(算法框架 vs 细节差异)
固定步骤(算法框架) 冲咖啡(子类细节) 煮茶(子类细节)
1. 煮水 煮山泉水 煮自来水
2. 冲泡 冲泡咖啡粉 冲泡铁观音茶叶
3. 倒入杯中 将咖啡倒入杯中 将茶水倒入杯中
4. 加辅料 加糖、加牛奶、加点醋 加糖、加柠檬、加生姜

(3)C++ 代码详解(对应案例实现)

  1. 代码整体结构​

通过 “抽象基类(定框架)+ 子类(实现细节)+ 测试函数(验证逻辑)” 实现模板方法模式,核心是基类的模板方法固定步骤,子类实现纯虚函数细节

  1. 逐部分解析​

①抽象基类:DrinkTemplate(定义算法框架)

#include 
using namespace std;

class DrinkTemplate 
{
public:
    // 1. 纯虚函数:需子类实现的“细节步骤”(延迟到子类)
    virtual void BoildWater() = 0;   // 煮水(细节不同)
    virtual void Brew() = 0;         // 冲泡(细节不同)
    virtual void PourInCup() = 0;    // 倒入杯中(细节不同)
    virtual void AddSomething() = 0; // 加辅料(细节不同)

    // 2. 模板方法:固定“算法步骤顺序”(核心!子类不可修改)
    void Make() 
    {
        BoildWater();  // 步骤1:煮水
        Brew();        // 步骤2:冲泡
        PourInCup();   // 步骤3:倒入杯中
        AddSomething();// 步骤4:加辅料
    }
};
  • 关键:Make() 是模板方法,直接定义 4 个步骤的执行顺序,确保算法结构不被改变;​

  • 纯虚函数(=0):标记 “需子类实现的细节”,强制子类覆盖,避免遗漏。

②子类 1:Coffee(实现 “冲咖啡” 细节)

//冲泡咖啡
class Coffee : public DrinkTemplate
{
public:
    // 重写纯虚函数:实现冲咖啡的每步细节
	virtual void BoildWater()//煮水
	{
		cout << "煮山泉水..." << endl;
	}
	virtual void Brew()//冲泡
	{
		cout << "冲泡咖啡..." << endl;
	}
	virtual void PourInCup()//倒入杯中
	{
		cout << "咖啡倒入杯中..." << endl;
	}
	virtual void AddSomething()//加辅料
	{
		cout << "加糖、加牛奶、加点醋..." << endl;
	}
};

③子类 2:Tea(实现 “煮茶” 细节)

//冲泡茶水
class Tea : public DrinkTemplate
{
public:
    // 重写纯虚函数:实现煮茶的每步细节
	virtual void BoildWater()//煮水
	{
		cout << "煮自来水..." << endl;
	}
	virtual void Brew()//冲泡
	{
		cout << "冲泡铁观音..." << endl;
	}
	virtual void PourInCup()//倒入杯中
	{
		cout << "茶水倒入杯中..." << endl;
	}
	virtual void AddSomething()//加辅料
	{
		cout << "加点糖、加柠檬、加生姜..." << endl;
	}
};

④测试函数:验证模板方法逻辑

// 测试函数:调用模板方法,无需关注子类细节
void test01() 
{
    // 1. 煮茶:通过子类对象调用模板方法Make()
    Tea* tea = new Tea;
    tea->Make();  // 自动按“煮水→冲泡→倒入→加辅料”执行,细节为Tea的实现

   	cout << "-------------" << endl;// 2. 冲咖啡:同理,步骤顺序不变,细节为Coffee的实现
    Coffee* coffee = new Coffee;
    coffee->Make();
}

// 主函数:程序入口
int main()
{
    test01();
    system("pause");
    return EXIT_SUCCESS;
}

⑤代码运行结果

煮自来水...
冲泡铁观音...
茶水倒入杯中...
加点糖、加柠檬、加生姜...
-------------
煮山泉水...
冲泡咖啡...
咖啡倒入杯中...
加糖、加牛奶、加点醋...
  • 结果说明:两个子类均按 Make() 定义的步骤执行,仅细节不同,验证了 “算法框架固定、细节延迟到子类” 的核心逻辑。

5.1.3模板方法的优缺点

优点:

  • 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
  • 模板方法模式是一种代码复用技术,它在类库设计中尤为重要,它提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当使用继承来实现代码复用。
  • 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。
  • 在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则。

缺点:

  • 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象。

5.1.4适用场景

  • 具有统一的操作步骤或操作过程;
  • 具有不同的操作细节;
  • 存在多个具有同样操作步骤的应用场景,但某些具体的操作细节却各不相同;

在抽象类中统一操作步骤,并规定好接口;让子类实现接口。这样可以把各个具体的子类和操作步骤解耦合。

5.2命令模式

  将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

  命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//医生类
class Doctor
{
public:
	void TreatEyes()
    {
		cout << "医生治疗眼病!" << endl;
	}
	void TreatHead()
    {
		cout << "医生治疗头疼!" << endl;
	}
	void TreatNose()
    {
		cout << "医生治疗鼻子!" << endl;
	}
};

//命令抽象类
class AbstractCommand
{
public:
	virtual void Execute() = 0;
};

//治疗眼睛指令(命令) 也可理解为病单
class CommandTreatEyes : public AbstractCommand
{
public:
	CommandTreatEyes(Doctor* doctor)
    {
		pDoctor = doctor;
	}
	virtual void Execute()
    {
		pDoctor->TreatEyes();
	}
private:
	Doctor* pDoctor;
};

//治疗头疼命令
class CommandTreatHead : public AbstractCommand
{
public:
	CommandTreatHead(Doctor* doctor)
    {
		pDoctor = doctor;
	}
	virtual void Execute(){
		pDoctor->TreatHead();
	}
private:
	Doctor* pDoctor;
};

//治疗鼻子命令
class CommandTreatNose : public AbstractCommand
{
public:
	CommandTreatNose(Doctor* doctor)
    {
		pDoctor = doctor;
	}
	virtual void Execute()
    {
		pDoctor->TreatNose();
	}
private:
	Doctor* pDoctor;
};

//测试
void test01()
{
	Doctor* doctor = new  Doctor;

	//创建病单(命令)
	AbstractCommand* commandEyes = new CommandTreatEyes(doctor);
	AbstractCommand* commandHead = new CommandTreatHead(doctor);
	AbstractCommand* commandNose = new CommandTreatNose(doctor);

	commandEyes->Execute();
	commandHead->Execute();
	commandNose->Execute();
}

int main()
{
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。

5.2.1命令模式中的角色和职责

Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。

ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。

Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。

Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。

5.2.2命令模式的案例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<list>
using namespace std;

//医生类
class Doctor
{
public:
	void TreatEyes()
    {
		cout << "医生治疗眼病!" << endl;
	}
	void TreatHead()
    {
		cout << "医生治疗头疼!" << endl;
	}
	void TreatNose()
    {
		cout << "医生治疗鼻子!" << endl;
	}
};

//命令抽象类
class AbstractCommand
{
public:
	virtual void Execute() = 0;
};

//治疗眼睛指令(命令) 也可理解为病单
class CommandTreatEyes : public AbstractCommand
{
public:
	CommandTreatEyes(Doctor* doctor)
    {
		pDoctor = doctor;
	}
	virtual void Execute()
    {
		pDoctor->TreatEyes();
	}
private:
	Doctor* pDoctor;
};

//治疗头疼命令
class CommandTreatHead : public AbstractCommand
{
public:
	CommandTreatHead(Doctor* doctor)
    {
		pDoctor = doctor;
	}
	virtual void Execute()
    {
		pDoctor->TreatHead();
	}
private:
	Doctor* pDoctor;
};

//治疗鼻子命令
class CommandTreatNose : public AbstractCommand
{
public:
	CommandTreatNose(Doctor* doctor)
    {
		pDoctor = doctor;
	}
	virtual void Execute()
    {
		pDoctor->TreatNose();
	}
private:
	Doctor* pDoctor;
};

//护士类 统一编制病单
class Nurse
{
public:
	void addCommand(AbstractCommand* command)
    {
		m_list.push_back(command);
	}
	//护士统一提交病单给医生
	void submitCommands()
    {
		for (list<AbstractCommand*>::iterator it = m_list.begin(); it != m_list.end();it ++)
        {
			(*it)->Execute();
		}
	}
	~Nurse()
    {
		for (list<AbstractCommand*>::iterator it = m_list.begin(); it != m_list.end(); it++)
        {
			if (NULL != *it){ delete *it; }
		}
	}
private:
	list<AbstractCommand*> m_list;
};

void test01()
{
	Doctor* doctor = new Doctor; //创建医生
	Nurse* nurse = new Nurse; //创建护士

	AbstractCommand* command = NULL;
	command = new CommandTreatEyes(doctor);
	nurse->addCommand(command);
	command = new CommandTreatHead(doctor);
	nurse->addCommand(command);
	command = new CommandTreatNose(doctor);
	nurse->addCommand(command);

	nurse->submitCommands(); //护士统一提交病单给医生

	delete nurse;
	delete doctor;
}

int main()
{
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

(1)核心角色与连接基础

  1. 客户端
    • 运行在用户主机上,负责发起请求(比如 “等级升级”),并按照预定义协议格式封装请求数据。
    • 依赖 Socket 建立与服务器的网络连接,相当于两端之间的 “通信管道”。
  2. 服务器
    • 运行在远程主机上,持续监听客户端的连接请求和数据发送。
    • 负责解析客户端发来的协议数据,执行业务逻辑(如校验等级、修改数据库),并返回处理结果。
  3. 数据库
    • 服务器的底层数据存储载体,保存用户的核心信息(如当前等级、经验值),供服务器读写操作。

(2)协议的本质与格式定义

协议是两端约定好的数据格式,目的是让双方能 “读懂” 对方的信息,你提到的 “字符串封装” 是一种简单易实现的方式。以 “等级升级” 为例,预定义的协议格式可以是:

[协议编号(int)][当前等级(int)][目标等级(int)]
  • 协议编号:1 代表 “升级请求”(可扩展:2 代表加金钱、3 代表任务提交等)
  • 示例数据:1 55 66 → 表示 “协议 1(升级),当前 55 级,目标 66 级”

(3)完整交互流程(对应等级升级案例)

  1. 客户端封装协议并发送

    • 客户端判断用户满足升级条件(如经验值达标),按照预定义格式拼接协议字符串 1 55 66
    • 通过已建立的 Socket 连接,将该字符串发送给服务器。
  2. 服务器监听并接收数据

    • 服务器处于持续监听状态,检测到客户端发来的数据后,读取并获取协议字符串 1 55 66
  3. 服务器解析协议并执行业务逻辑

    • 解析协议

      :按照约定格式拆分字符串,提取关键信息:

      • 协议编号 1 → 识别为 “升级请求”;
      • 当前等级 55、目标等级 66 → 获取升级相关参数。
    • 合法性校验:服务器查询数据库中该用户的实际等级(是否为 55 级)、经验值是否满足升级到 66 级的要求,判断是否存在作弊(如篡改等级参数)。

    • 执行操作:校验通过后,服务器修改数据库中用户的等级(如从 55 级更新为 66 级)。

  4. 服务器返回处理结果

    • 服务器按照同样的协议格式,封装 “升级成功” 的响应数据(如 1 0 66,其中 0 代表成功)。
    • 通过 Socket 管道将响应数据发送回客户端。
  5. 客户端接收响应并反馈用户

    • 客户端解析服务器的响应数据,确认升级成功后,通过弹窗、界面刷新等方式告知用户(如 “等级已提升至 66 级”)。

(4)协议设计的核心意义

  1. 统一 “语言”:解决两端 “不能直接对话” 的问题,确保数据传递无歧义。
  2. 扩展性强:新增功能时,只需定义新的协议编号(如 4 代表 “装备强化”)和对应的参数格式,无需修改整体通信框架。
  3. 便于校验:协议中的参数(如当前等级)可用于服务器校验请求合法性,防止恶意篡改数据。

(5)服务器的协议处理扩展性设计

服务器要支持多种协议(升级、加金钱、任务提交等),需要预先定义不同协议对应的处理逻辑

  • 设计一个 “协议 - 处理器” 映射表:如 协议1 → 升级处理函数协议2 → 加金钱处理函数
  • 当服务器解析出协议编号后,直接调用对应的函数处理,无需写大量 if-else 判断,代码更简洁、易维护。

5.2.3命令模式练习题

联想路边撸串烧烤场景, 有烤羊肉,烧鸡翅命令,有烤串师傅,和服务员MM。根据命令模式,设计烤串场景。

5.2.4命令模式的优缺点

优点:

  • 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
  • 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
  • 可以比较容易地设计一个命令队列或宏命令(组合命令)。

缺点:

  • 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。

5.2.5适用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。 (3) 系统需要将一组操作组合在一起形成宏命令。

5.3策略模式

  策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

5.3.1策略模式中的角色和职责

Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。

Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。

ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

5.3.2策略模式案例

// 03 策略模式.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
using namespace std;

//抽象武器 武器策略
class WeaponStrategy
{
public:
	virtual void UseWeapon() = 0;

};

class Knife : public WeaponStrategy
{
public:
	virtual void UseWeapon()
	{
	    cout << "使用匕首..." << endl;
	}
};

class AK47 : public WeaponStrategy
{
public:
	virtual void UseWeapon()
	{
		cout << "使用AK47..." << endl;
	}
};


class Character
{
public:
	void setWeapon(WeaponStrategy* weapon)
	{
		this->pWeapon = weapon;
	}
	void ThrowWeapon()
	{
		this->pWeapon->UseWeapon();
	}
public:
	WeaponStrategy* pWeapon;
};

void test01()
{
	//创建角色
	Character* character = new Character;

	//武器策略
	WeaponStrategy* knife = new Knife;
	WeaponStrategy* ak47 = new AK47;

	character->setWeapon(knife);
	character->ThrowWeapon();

	character->setWeapon(ak47);
	character->ThrowWeapon();

	delete ak47;
	delete knife;
	delete character;
}

int main()
{
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

(1)策略模式核心定义

  策略模式的核心是定义一系列算法,将每个算法封装起来,并使它们可以相互替换。其本质是把可变的行为(算法)与不变的主体分离,让行为可以独立于使用它的主体动态切换。

(2)策略模式解决的核心问题

解决继承带来的 “行为固定化” 局限性:

  • 传统继承方式下,若一个类继承了某个具体行为(如 “人继承宝马车”),则该类只能固定使用这个行为(只能开宝马),无法灵活切换;
  • 策略模式将可变行为抽象为独立的 “策略”,通过动态设置的方式让主体(如 “人”“游戏角色”)切换不同行为(如开拖拉机、用 AK47)。

(3)案例场景:游戏角色使用武器

①核心思路

把 “使用武器” 的行为抽象为策略接口,不同武器(匕首、AK47)作为具体策略实现,游戏角色(Character)持有策略接口的指针,通过 set 方法动态切换武器策略。

②代码实现分步解析
A. 抽象策略类(武器策略接口)

定义所有武器策略的统一接口,规范 “使用武器” 的行为:

// 抽象武器策略类(策略接口)
class WeaponStrategy
{
public:
    // 纯虚函数,定义使用武器的统一接口
    virtual void UseWeapon() = 0;
};
B. 具体策略类(具体武器实现)

实现抽象策略接口,封装具体武器的使用逻辑:

// 匕首策略(具体策略1)
class Knife : public WeaponStrategy
{
public:
    // 实现匕首的使用逻辑
    virtual void UseWeapon()
    {
        cout << "使用匕首..." << endl;
    }
};

// AK47策略(具体策略2)
class AK47 : public WeaponStrategy
{
public:
    // 实现AK47的使用逻辑
    virtual void UseWeapon()
    {
        cout << "使用AK47..." << endl;
    }
};
C. 上下文类(使用策略的主体:游戏角色)

持有抽象策略类的指针,提供 set 方法动态切换策略,并封装策略的调用逻辑:

class Character
{
public:
    // 持有抽象武器策略的指针(核心:面向抽象编程)
    WeaponStrategy* pWeapon;

    // 动态设置武器策略的方法
    void setWeapon(WeaponStrategy* weapon)
    {
        this->pWeapon = weapon;
    }

    // 调用当前策略的行为(使用武器)
    void ThrowWeapon()
    {
        this->pWeapon->UseWeapon(); // 委托给具体策略执行
    }
};
D. 测试逻辑(使用策略模式)

创建角色和不同武器策略,动态切换并调用:

void test01()
{
    // 1. 创建角色(上下文)
    Character* character = new Character;

    // 2. 创建具体武器策略
    WeaponStrategy* knife = new Knife;
    WeaponStrategy* ak47 = new AK47;

    // 3. 动态设置策略:使用匕首
    character->setWeapon(knife);
    character->ThrowWeapon(); // 输出:使用匕首...

    // 4. 动态切换策略:使用AK47
    character->setWeapon(ak47);
    character->ThrowWeapon(); // 输出:使用AK47...

    // 5. 释放内存
    delete ak47;
    delete knife;
    delete character;
}

int main()
{
    test01();
    system("pause");
    return EXIT_SUCCESS;
}

(3)策略模式核心特点与关键理解

①核心结构
  • 抽象策略(Strategy):定义算法 / 行为的统一接口(如 WeaponStrategy);
  • 具体策略(ConcreteStrategy):实现抽象策略,封装具体算法(如 Knife、AK47);
  • 上下文(Context):持有抽象策略指针,提供策略的设置和调用(如 Character)。
②核心优势
  • 行为与主体解耦:新增武器(如狙击枪)只需新增具体策略类,无需修改角色类;
  • 动态切换行为:通过 set 方法可在运行时灵活切换角色的武器;
  • 避免多重继承:替代 “角色继承匕首 / AK47” 的不合理设计,符合 “合成复用原则”。
③ 关键理解点
  • 策略模式的 “策略” 本质是 “可变的行为 / 算法”,无需纠结命名(如案例中 “武器策略”=“武器使用算法”);
  • 核心是 “面向抽象编程”:上下文类只依赖抽象策略接口,不依赖具体实现,保证扩展性。

(4)类比案例(辅助理解)

讲课中提到的 “人开不同车上班” 案例与武器案例逻辑一致:

  • 抽象策略:抽象车(定义 “开车” 接口);
  • 具体策略:宝马、拖拉机(实现 “开宝马”“开拖拉机”);
  • 上下文:人(持有抽象车指针,set 方法切换车,调用 “开车” 行为)。

5.3.3策略模式练习题

商场促销有策略A(0.8折) 策略B(消费满200,返现100),用策略模式模拟场景。

5.3.4策略模式的优缺点

优点:

  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 使用策略模式可以避免多重条件选择语句。多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。
  • 策略模式提供了一种算法的复用机制。由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
  • 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。

5.3.5适用场景

准备一组算法,并将每一个算法封装起来,使得它们可以互换。

5.4观察者模式

  随着交通信号灯的变化,汽车的行为也将随之而变化,一盏交通信号灯可以指挥多辆汽车。

  观察者模式是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

观察者模式核心定义

核心思想:当一个对象(观察目标)的状态发生变化时,会自动通知所有依赖它的对象(观察者),并触发观察者的状态或行为变化,用于描述 “一个对象变化引发多个对象变化” 的场景。

5.4.1观察者模式中的角色和职责

Subject(被观察者或目标,抽象主题):被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。

ConcreteSubject(具体被观察者或目标,具体主题):被观察者的具体实现。包含一些基本的属性状态及其他操作。

Observer(观察者):接口或抽象类。当Subject的状态发生变化时,Observer对象将通过一个callback函数得到通知。

ConcreteObserver(具体观察者):观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。

5.4.2观察者模式案例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<list>
using namespace std;

//抽象观察者
class AbstractObserver
{
public:
	virtual void Update() = 0;
};

//说悄悄话的同学
class WhisperStudent : public AbstractObserver
{
public:
	virtual void Update()
    {
		cout << "停止说悄悄话,专心学习..." << endl;
	}
};

//抄作业的同学
class CopyHomeWorkdStudent : public AbstractObserver
{
public:
	virtual void Update()
    {
		cout << "停止抄作业,专心自己写作业..." << endl;
	}
};

//玩手机的同学
class PlayTelephoneStudent : public AbstractObserver
{
public:
	virtual void Update()
    {
		cout << "停止玩手机,专心看书..." << endl;
	}
};

//抽象通知者 也就是被不好好学习的同学观察的目标
class AbstractInformer
{
public:
	virtual void addObserver(AbstractObserver*) = 0; //增加观察者
	virtual void deleteObserver(AbstractObserver*) = 0; //删除观察者
	virtual void Notify() = 0; //有情况通知所有观察者
};

//具体通知者 也就是被观察的目标 班长
class MonitorClass : public AbstractInformer
{
public:
	virtual void addObserver(AbstractObserver* observer)
    {
		m_list.push_back(observer);
	}
	virtual void deleteObserver(AbstractObserver* observer)
    {
		m_list.remove(observer);
	}
	virtual void Notify()
    {
		for (list<AbstractObserver*>::iterator it = m_list.begin(); it != m_list.end(); it ++)
        {
			(*it)->Update();
		}
	}
private:
	list<AbstractObserver*> m_list;
};

void test01()
{
	//三名观察者
	AbstractObserver* stu1 = new WhisperStudent;
	AbstractObserver* stu2 = new CopyHomeWorkdStudent;
	AbstractObserver* stu3 = new PlayTelephoneStudent;

	//创建班长
	AbstractInformer* monitor = new MonitorClass;

	//告诉班长,老师来了那些人需要通知
	monitor->addObserver(stu1);
	monitor->addObserver(stu2);
	monitor->addObserver(stu3);

	cout << "发生特殊情况... 老师来了... 快通知哪些学生..." << endl;
	monitor->Notify();

	cout << "平日里说悄悄话的这个同学和班长关系不好,班长不想通知他" << endl;
	monitor->deleteObserver(stu1);

	cout << "老师又来了... 班长通知其他人 但是这次不通知说悄悄话的那个同学" << endl;
	monitor->Notify();

	//释放资源
	delete monitor;
	delete stu3;
	delete stu2;
	delete stu1;
}

int main()
{
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

(1)生活案例(帮助理解)

讲课中用 3 个通俗案例解释观察者模式,便于快速建立认知:

案例场景 观察目标 观察者 变化与通知逻辑
交通信号灯与汽车 红绿灯 多辆汽车 红绿灯状态变(红灯→绿灯)→ 通知所有汽车,汽车状态从 “停”→“走”
班级盯班主任 班长(盯门) 上课违纪学生 班主任来(班长状态变化)→ 班长通知学生,学生从 “玩手机 / 睡觉”→“假装学习”
游戏 BOSS 与英雄 BOSS 多个英雄 BOSS 状态变(活着→死亡)→ 通知所有英雄,英雄从 “撸 BOSS(攻击)”→“待机”;若英雄阵亡(如 HeroC),则从观察者列表删除,不再通知

(2)观察者模式核心角色

观察者模式通过 4 个核心角色实现,每个角色对应代码中的具体类,职责明确:

角色名称 核心职责 代码对应(C++)
抽象观察者 定义观察者的统一接口(需包含 “接收通知后更新状态” 的方法) AbstractHero类(纯虚函数Update())
具体观察者 实现抽象观察者的接口,是实际接收通知并变化的对象 HeroA/HeroB/HeroC/HeroD/HeroE类
抽象观察目标 定义观察目标的统一接口:包含 “添加观察者”“删除观察者”“通知所有观察者” 的方法 AbstractBoss类(纯虚函数addHero()/deleteHero()/notify())
具体观察目标 实现抽象观察目标的接口,维护观察者列表,状态变化时触发通知 BOSSA类(维护list*>观察者列表)

(3)C++ 代码详细解析

代码围绕 “游戏 BOSS 与英雄” 案例实现,分 3 个模块解析:

  1. 头文件与全局声明​

    #include  // 用于输出状态
    
    #include  用于存储观察者列表(动态增删观察者)
    
    using namespace std;  // 简化代码书写
    
  2. 抽象类定义(核心接口)​

    (1)抽象观察者:AbstractHero

    • 职责:定义所有英雄(观察者)的统一接口,必须实现 “接收通知后更新状态” 的Update()方法

    • 代码:

      class AbstractHero
      {
      public:
          // 纯虚函数:接收通知后更新状态(观察者的核心行为)
          virtual void Update() = 0;  
      };
      

    (2)抽象观察目标:AbstractBoss

    • 职责:定义 BOSS(观察目标)的统一接口,包含 “管理观察者” 和 “通知观察者” 的核心方法

    • 代码:

      class AbstractBoss
      {
      public:
          virtual void addHero(AbstractHero* hero) = 0;    // 添加观察者(英雄加入“被通知列表”)
          virtual void deleteHero(AbstractHero* hero) = 0; // 删除观察者(英雄阵亡,移出列表)
          virtual void notify() = 0;                       // 通知所有观察者(BOSS状态变,触发通知)
      };
      
  3. 具体类实现(实际业务逻辑)​

    (1)具体观察者:HeroA~HeroE

    • 职责:实现AbstractHero的Update()方法,定义英雄接收通知后的具体行为(从 “撸 BOSS”→“待机”)

    • 代码(以 HeroA 为例,HeroB~HeroE 逻辑一致):

      //具体观察者
      class HeroA : public AbstractHero
      {
      public:
      	HeroA() { cout << "英雄正在撸BOSS..." << endl; }// 构造函数:初始化英雄状态(初始为“撸BOSS”)
      	virtual void Update() { cout << "英雄A停止撸BOSS,待机状态..." << endl; } // 实现Update():接收BOSS死亡通知后,状态变为“待机”
      };
      
      class HeroB : public AbstractHero
      {
      public:
      	HeroB() { cout << "英雄正在撸BOSS..." << endl; }
      	virtual void Update() { cout << "英雄B停止撸BOSS,待机状态..." << endl; }
      };
      
      class HeroC : public AbstractHero
      {
      public:
      	HeroC() { cout << "英雄正在撸BOSS..." << endl; }
      	virtual void Update() { cout << "英雄C停止撸BOSS,待机状态..." << endl; }
      };
      
      class HeroD : public AbstractHero
      {
      public:
      	HeroD() { cout << "英雄正在撸BOSS..." << endl; }
      	virtual void Update() { cout << "英雄D停止撸BOSS,待机状态..." << endl; }
      };
      
      class HeroE : public AbstractHero
      {
      public:
      	HeroE() { cout << "英雄正在撸BOSS..." << endl; }
      	virtual void Update() { cout << "英雄E停止撸BOSS,待机状态..." << endl; }
      };
      

    (2)具体观察目标:BOSSA

    • 职责:实现AbstractBoss的 3 个方法,维护观察者列表,状态变化时遍历通知所有观察者

    • 代码:

      class BOSSA : public AbstractBoss
      {
      public:
          // 1. 添加观察者:将英雄加入list列表(后续会被通知)
          virtual void addHero(AbstractHero* hero) 
          {
              pHeroList.push_back(hero); 
          }
      
          // 2. 删除观察者:将英雄从list列表移除(如英雄阵亡,不再通知)
          virtual void deleteHero(AbstractHero* hero) 
          {
              pHeroList.remove(hero); 
          }
      
          // 3. 通知所有观察者:遍历list,调用每个英雄的Update()方法
          virtual void notify() 
          {
              for (listiterator it = pHeroList.begin(); it != pHeroList.end(); it++)
              {
                  (*it)->Update();  // 触发观察者状态更新
              }
          }
      
      private:
          // 存储观察者列表:用list实现动态增删(支持英雄阵亡后移除)
          listeroList; 
      };
      
  4. 测试函数(模拟业务场景)​

    test01()函数模拟 “英雄撸 BOSS→HeroC 阵亡→BOSS 死亡→通知剩余英雄” 的完整流程:

    void test01()
    {
        // 1. 创建观察者:5个英雄(初始状态“正在撸BOSS”)
        AbstractHero* heroA = new HeroA;
        AbstractHero* heroB = new HeroB;
        AbstractHero* heroC = new HeroC;
        AbstractHero* heroD = new HeroD;
        AbstractHero* heroE = new HeroE;
    
        // 2. 创建观察目标:BOSS A
        AbstractBoss* bossA = new BOSSA;
    
        // 3. 给BOSS添加观察者:5个英雄加入“被通知列表”
        bossA->addHero(heroA);
        bossA->addHero(heroB);
        bossA->addHero(heroC);
        bossA->addHero(heroD);
        bossA->addHero(heroE);
    
        // 4. 模拟事件1:HeroC阵亡→从BOSS的观察者列表删除,不再通知
        cout <HeroC阵亡..." <;
        bossA->deleteHero(heroC);
    
        // 5. 模拟事件2:BOSS死亡→触发通知,剩余英雄更新状态
        cout <通知其他英雄停止攻击,抢装备..." <
        bossA->notify();
    }
    
    // 主函数:执行测试
    int main()
    {
        test01();
        system("pause");  // 暂停控制台,查看输出
        return EXIT_SUCCESS;
    }
    
  5. 代码运行结果(与逻辑对应)

    英雄A正在撸BOSS...
    英雄B正在撸BOSS...
    英雄C正在撸BOSS...
    英雄D正在撸BOSS...
    英雄E正在撸BOSS...
    HeroC阵亡...
    Boss死了...通知其他英雄停止攻击,抢装备...
    英雄A停止撸BOSS,待机状态...
    英雄B停止撸BOSS,待机状态...
    英雄D停止撸BOSS,待机状态...
    英雄E停止撸BOSS,待机状态...
    

5.4.3观察者模式的优缺点

优点:

  • 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
  • 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
  • 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

缺点:

  • 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

5.4.4适用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

参考资料来源:黑马程序员

posted @ 2025-12-23 10:51  CodeMagicianT  阅读(35)  评论(0)    收藏  举报