c++ 语言教程——16面向对象设计模式(五)

文章目录

今天讲解设计模式中的行为型模式中的责任链模式、命令模式、备忘录模式、状态模式

责任链模式

责任链模式(Chain of Responsibility Pattern),在该模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任

1.要注意的是:一个请求到链的最后可能也没有处理,所以一定要配置得当.

2.责任链模式并不创建责任链。责任链的创建必须由系统的其它部分创建出来。

3.责任链模式降低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。一个链可以是一条线,一个树,也可以是一个环

cpp 复制代码
// 请求
class Request
{
public:
	int m_nNumber;
};
 
// 管理者
class Manager
{
public:
	Manager(string temp) { name = temp; }
	void SetSuccessor(Manager* temp) { manager = temp; }
	virtual void GetRequest(Request* request) = 0;
protected:
	Manager* manager;
	string name;
};
 
// 经理
class CommonManager : public Manager
{
public:
	CommonManager(string strTemp) : Manager(strTemp) {}
	virtual void GetRequest(Request* request);
};
 
void CommonManager::GetRequest(Request* request)
{
	if (request->m_nNumber>=0 && request->m_nNumber<1000)
	{
		cout << name << " 处理了请求: " << request->m_nNumber << endl;
	}
	else
	{
		manager->GetRequest(request);
	}
}
 
// 总监
class Majordomo : public Manager
{
public:
	Majordomo(string strTemp) : Manager(strTemp) {}
	virtual void GetRequest(Request* request);
};
 
void Majordomo::GetRequest(Request* request)
{
	if (request->m_nNumber <= 5000)
	{
		cout << name << " 处理了请求: " << request->m_nNumber << endl;
	}else
	{
		manager->GetRequest(request);
	}
}
 
//总经理  
class GeneralManager: public Manager  
{  
public:  
	GeneralManager(string name):Manager(name) {}  
	virtual void GetRequest(Request* request)  //总经理可以处理所有请求  
	{ 		
		cout << name << " 处理了请求: " << request->m_nNumber << endl;		
	}  
};

 
int main()
{
    cout<<"Chain Test!"<<endl;
	
	Manager* common = new CommonManager("张经理");
	Manager* major = new Majordomo("李总监");
	GeneralManager* general  = new GeneralManager("赵总");
	common->SetSuccessor(major);
	major->SetSuccessor(general);
	Request* rq = new Request();
 
	rq->m_nNumber = 999;
	common->GetRequest(rq);
	
	rq->m_nNumber = 4999;
	common->GetRequest(rq);
 
	rq->m_nNumber = 6999;
	common->GetRequest(rq);
 
	delete rq;
	delete major;
	delete common;
	delete general;

    cout<<"finish!"<<endl;
	return 0;
}

优点:

1.解耦:责任链模式将发送者和接收者解耦,提高了系统的灵活性和可扩展性

2.简化对象:责任链模式可以将多个对象简化为一个对象,减少了系统的复杂性。

3.动态组合:责任链模式可以动态组合处理对象,提高了系统的灵活性和可扩展性。

缺点:

1.可能导致性能下降:由于责任链模式需要遍历整个链表,这可能会导致性能下降。

2.可能导致请求无法处理:如果责任链没有正确配置,可能会导致请求无法得到处理

命令模式

命令模式(Command Pattern)是将一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

适用性:

在下面的情况下应当考虑使用命令模式:

1.使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。

2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。

3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。

4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

cpp 复制代码
// 面包师傅
class BreadCook
{
public:
	void MakeBread() { cout << "烤一个面包" << endl; }
	void MakeRedBeanBread() { cout << "烤一个红豆面包" << endl; }
};
 
 
// 抽象命令类
class Command
{
public:
	Command(BreadCook* temp) { receiver = temp; }
	virtual void ExecuteCmd() = 0;
 
protected:
	BreadCook* receiver;
};
 
// 烤面包命令
class MakeBreadCmd : public Command
{
public:
	MakeBreadCmd(BreadCook* temp) : Command(temp) {}
	virtual void ExecuteCmd() { receiver->MakeBread(); }
};
 
// 烤红豆面包命令
class MakeRedBeanBreadCmd : public Command
{
public:
	MakeRedBeanBreadCmd(BreadCook* temp) : Command(temp) {}
	virtual void ExecuteCmd() { receiver->MakeRedBeanBread(); }
};
 
// 服务员类
class Waiter
{
public:
	void SetCmd(Command* temp);
 
	// 通知执行
	void Notify();
protected:
	vector<Command*> m_commandList;
};
 
void Waiter::SetCmd(Command* temp)
{
	m_commandList.push_back(temp);
	cout << "增加订单" << endl;
}
 
void Waiter::Notify()
{
	vector<Command*>::iterator it;
	for (it=m_commandList.begin(); it!=m_commandList.end(); ++it)
	{
		(*it)->ExecuteCmd();
	}
}
 
int main()
{
    cout<<"Command Test!"<<endl;
	
	BreadCook* cook = new BreadCook();
	Command* cmd1 = new MakeBreadCmd(cook);
	Command* cmd2 = new MakeRedBeanBreadCmd(cook);
	Waiter* girl = new Waiter();
 
	// 点菜
	girl->SetCmd(cmd1);
	girl->SetCmd(cmd2);
 
	// 服务员通知
	girl->Notify();

    cout<<"finish!"<<endl;
	return 0;
}

优点:

命令模式将发送者和接收者解耦,使得两者可以分别独立变化。

扩展性好,新的命令可以很容易地添加和维护,不影响现有系统。

使用对象来存储命令,很适用于开发回滚和撤销操作。

可以使用队列将命令进行缓存,实现延迟执行或者异步处理。

缺点:

增加了一些额外的抽象层次,使代码结构变得复杂。

命令的具体操作包含了对象的动态创建和销毁,性能开销大。

对象之间存在着多层次的依赖,维护变得困难,不易于bug定位和调试

备忘录模式

备忘录模式(Memento Pattern)是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

文本编辑器: 保存文档的历史状态,以便用户能够撤销和重做操作。

游戏存档: 在游戏中保存玩家的状态,以便在需要时恢复。

事务处理: 在数据库操作中保存事务的状态,以支持回滚操作。

cpp 复制代码
//玩家主角相关的备忘录类
class FighterMemento
{
private:
    //构造函数,用private修饰以防止在外部被随意创建
    FighterMemento(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

private:
    //提供一些供Fighter类来访问的接口,用private修饰防止被任意类访问
    friend class Fighter; //友元类Fighter可以访问本类的私有成员函数
    int getLife() const { return m_life; }
    void setLife(int life) { m_life = life; }
    int getMagic() const { return m_magic; }
    void setMagic(int magic) { m_magic = magic; }
    int getAttack() const { return m_attack; }
    void setAttack(int attack) { m_attack = attack; }
private:
    //玩家主角类中要保存起来的数据,就放到这里来
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
};


//玩家主角类
class Fighter
{
public:
    //构造函数
    Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}


public:
    //将玩家数据写入备忘录(创建备忘录,并在其中存储了当前状态)
    FighterMemento* createMomento()
    {
        return new FighterMemento(m_life, m_magic, m_attack);
    }
    //从备忘录中恢复玩家数据
    void restoreMomento(FighterMemento* pfm)
    {
        m_life = pfm->getLife();
        m_magic = pfm->getMagic();
        m_attack = pfm->getAttack();
    }
    //为测试目的引入的接口,设置玩家的生命值为0(玩家死亡)
    void setToDead()
    {
        m_life = 0;
    }
    //用于输出一些信息
    void displayInfo()
    {
        cout << "玩家当前的生命值、魔法值、攻击力分别为:" << m_life << "," << m_magic << "," << m_attack << endl;
    }


private:
    //角色属性
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
    //......其他数据略
};

//---------------------
//管理者(负责人)类
class FCareTaker
{
public:
    //构造函数
    FCareTaker(FighterMemento* ptmpfm) :m_pfm(ptmpfm) {} //形参是指向备忘录对象的指针

    //获取指向备忘录对象的指针
    FighterMemento* getMemento()
    {
        return m_pfm;
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfm = ptmpfm;
    }
private:
    FighterMemento* m_pfm; //指向备忘录对象的指针
};

//-----------------
//支持多个快照的负责人(管理者)类
class FCareTaker2
{
public:
    //析构函数用于释放资源
    ~FCareTaker2()
    {
        for (auto iter = m_pfmContainer.begin(); iter != m_pfmContainer.end(); ++iter)
        {
            delete (*iter);
        } //end for
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfmContainer.push_back(ptmpfm);
    }
    //获取指向备忘录对象的指针
    FighterMemento* getMemento(int index)
    {
        auto iter = m_pfmContainer.begin();
        for (int i = 0; i <= index; ++i)
        {
            if (i == index)
                return (*iter);
            else
                ++iter;
        } //end for
        return nullptr;
    }
private:
    //存储备忘录对象指针的容器
    vector<FighterMemento*> m_pfmContainer;  //#include <vector>
};

int main()
{

    Fighter* p_fighter = new Fighter(800, 200, 300);
    //(1)显示玩家信息
    p_fighter->displayInfo();

    //(2)为玩家创建一个备忘录对象(存档)
    FCareTaker* pfcaretaker = new FCareTaker(p_fighter->createMomento());

    //(3)玩家与BOSS开始战斗
    cout << "玩家与BOSS开始进行激烈的战斗------" << endl;
    p_fighter->setToDead();   //玩家被BOSS击败
    p_fighter->displayInfo(); //显示玩家信息


    cout << "玩家通过备忘录恢复自己的信息------" << endl;
    p_fighter->restoreMomento(pfcaretaker->getMemento());
    p_fighter->displayInfo(); //显示玩家信息

    //(5)释放资源
    delete pfcaretaker->getMemento();
    delete pfcaretaker; //新增
    delete p_fighter;

    Fighter* p_fighter2 = new Fighter(800, 200, 300);
    FCareTaker2* pfcaretaker2 = new FCareTaker2();

    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第一次快照,生命值为800
    p_fighter2->setToDead(); // 改变玩家主角的生命值
    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第二次快照,生命值为0

    p_fighter2->displayInfo(); // 当前生命值为0
    cout << "------------------" << endl;

    // 恢复第一次快照,生命值恢复为800
    p_fighter2->restoreMomento(pfcaretaker2->getMemento(0));
    p_fighter2->displayInfo(); // 玩家主角生命值应恢复为800

    // 释放资源
    delete p_fighter2;
    delete pfcaretaker2;

    return 0;
}

优点

封装性: 备忘录模式将对象的状态封装在备忘录中,使得外部无法访问对象的内部状态。

简化恢复操作: 通过备忘录,可以方便地恢复对象到之前的状态,而不需要了解对象的具体实现细节。

历史记录管理: 可以轻松实现对象状态的历史记录功能。

缺点

内存消耗: 如果对象状态庞大或频繁创建备忘录,可能导致内存消耗增大。

状态管理复杂性: 在需要管理多个状态时,备忘录的数量可能会迅速增加,管理起来会变得复杂。

状态模式

状态模式(State Pattern)就是对象的行为,依赖于它所处的状态。该模式的核心在于将状态相关的行为封装到独立的状态类中,使得对象的行为随状态而变化,从而减少冗余的条件判断。

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简单化。

当一个对象行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。

cpp 复制代码
class Boss;
class BossStatus_Musth;
class BossStatus_Dead;
class BossStatus_Normal;

class BossStatus
{
public:
    virtual void Attacked(int attack,Boss* pBoss) =0;
    virtual string GetStatus(void) =0;
    virtual ~BossStatus(){};
};




//普通状态类
class BossStatus_Normal :public BossStatus
{
public:
	void Attacked(int attack, Boss* pBoss);

    string GetStatus(void) override { return status;};

    string status="普通状态";
};



//狂暴状态类
class BossStatus_Musth :public BossStatus
{
public:
	void Attacked(int attack, Boss* pBoss);

    string GetStatus(void) override { return status;};
    string status="狂暴状态";

};

//死亡状态类
class BossStatus_Dead :public BossStatus
{
public:
	void Attacked(int attack, Boss* pBoss);

    string GetStatus(void) override { return status;};

    string status="死亡状态";

};



//怪物类
class Boss
{
public:
	Boss(int life){
        m_life=life;
        m_pState=new BossStatus_Normal();
        cout << "BOSS刷新,当前处于普通状态,血量:" << life<<endl;
    };
	~Boss(){};

public:
	void Attacked(int power){
        m_pState->Attacked(power, this);
    }

public:
    int GetLife() //获取怪物血量
	{
		return m_life;
	}
	void SetLife(int life) //设置怪物血量
	{
		m_life = life;
	}
	BossStatus* getCurrentState() //获取怪物当前状态
	{
		return m_pState;
	}
	void setCurrentState(BossStatus* pstate) //设置怪物当前状态
	{
        if(m_pState!=nullptr)delete m_pState;
		m_pState = pstate;
	}

private:
	int            m_life;    //血量(生命值)
	BossStatus* m_pState;  //持有状态对象
};




void BossStatus_Normal::Attacked(int attack, Boss* pBoss) 
{
        int life = pBoss->GetLife();  	
        if ((life - attack) > 200)       
        {
            pBoss->SetLife(life - attack); 
            cout << "BOSS 受到"<<attack<<"点攻击,剩余血量"<<pBoss->GetLife()<<",处于"<<status<<",对主角进行反击!" << endl;

        }
        else
        {
            pBoss->setCurrentState(new BossStatus_Musth());
            pBoss->getCurrentState()->Attacked(attack, pBoss);
        }

}

void BossStatus_Musth::Attacked(int attack, Boss* pBoss) 
{
        int life = pBoss->GetLife();  	
        if ((life - attack) > 0)       
        {
            pBoss->SetLife(life - attack); 
            cout << "BOSS 受到"<<attack<<"点攻击,剩余血量"<<pBoss->GetLife()<<",处于"<<status<<",对主角进行疯狂的反击!" << endl;

        }
        else
        {
            pBoss->setCurrentState(new BossStatus_Dead());
            pBoss->getCurrentState()->Attacked(attack, pBoss);
        }

}

void BossStatus_Dead::Attacked(int attack, Boss* pBoss) 
 {

    cout << "BOSS 已经死亡"<<endl;

}

int main()
{

    cout<<"State Test!"<<endl;

    Boss boss(500);
    boss.Attacked(100);
    boss.Attacked(200);
    boss.Attacked(100);
    boss.Attacked(100);
    boss.Attacked(100);


    cout<<"finish!"<<endl;
    return 0;
}

优点

清晰的代码结构:通过将状态行为封装在状态对象中,避免了大量的条件语句,使代码更加清晰易读。

易于扩展:增加新状态只需添加新的状态类,而无需修改现有代码,符合开闭原则。

灵活的状态切换:状态之间的切换逻辑集中管理,便于维护和修改。

缺点

类数量增加:每种状态都需要一个具体的状态类,可能导致类的数量增加,从而增加系统的复杂性。

管理复杂性:状态之间的关系和切换逻辑可能变得难以管理,尤其是在状态较多时,可能需要额外的设计来处理状态转移

相关推荐
Alsn862 小时前
30.登录用户名密码 RSA 加密传输-后端为java
java·开发语言
老王熬夜敲代码2 小时前
C++的decltype
开发语言·c++·笔记
lxp1997412 小时前
PHP框架自带队列--更新中
开发语言·php
MoonBit月兔2 小时前
海外开发者实践分享:用 MoonBit 开发 SQLC 插件(其三)
java·开发语言·数据库·redis·rust·编程·moonbit
问道飞鱼2 小时前
【Rust编程知识】在 Windows 下搭建完整的 Rust 开发环境
开发语言·windows·后端·rust·开发环境
闻缺陷则喜何志丹2 小时前
【C++组合数学】P8106 [Cnoi2021] 数学练习|普及+
c++·数学·洛谷·组合数学
jllllyuz2 小时前
C# 面向对象图书管理系统
android·开发语言·c#
wuguan_2 小时前
C#文件读取
开发语言·c#·数据读写
hoiii1872 小时前
基于C#的PLC串口通信实现
开发语言·c#·plc