C++设计模式笔记

C++设计模式笔记,第1张

C++设计模式笔记
  • 前言
    • 模块方法 (Template Method)
    • 策略模式 (Strategy)
    • 观察者模式 (Observer)
    • 装饰器 (Decorator)
    • 桥模式 (Bridge)

前言

之前面试游戏各大厂跟游戏新贵的时候,总是被问到“设计模式”相关内容,但我由于在本科和研究生期间并没有学过这个东西,唯一的了解也就是在腾讯实习的时候,组内开发的游戏使用了MVC架构。


痛定思痛,提交了毕设论文盲审之后,打算了解一下“设计模式”是什么,所以本文打算记录一下b站《C++设计模式》的学习经历。


我自己对于“设计模式”的理解是,对于“面向对象语言如何设计整个项目的框架”这个问题,程序员先辈们进行很多尝试并且总结出了很多经验,他们对代码架构的优秀经验就是“设计模式”,设计模式对游戏各个模块的设计是很重要的。


优秀的项目需要有合理的架构,比如当你完成一个C++图像处理库的所有功能之后,你需要为以后可能添加的功能做好设想,当以后添加另一个功能时,需要改变这个项目的多少部分,是否需要重新部署,这也就是设计模式需要考虑的东西。


模块方法 (Template Method)

Cpp库里面要实现一个固定流程的方法,这个方法包含五个部分,Step1~Step5,其中某些步骤是固定的,例如Step1、Step3、Step5,另一些步骤例如Step2Step4则是由用户自己定义的。


由于这个方法步骤固定,也就是Step1~Step5肯定依次执行,所以可以在Library的Run函数中定义这个方法,在Library内部实现Step1、Step3、Step5,并将Step2Step4定义为纯虚函数,开放给用户定义,这个Library的Run方法依然是封闭的,模块方法拥有着更好的封装性。


#include 
using namespace std;

//程序库开发人员
class Library {
  public:
    //稳定 template method
    void Run() {
        Step1();
        if (Step2()) {
            //支持变化 ==> 虚函数的多态调用
            Step3();
        }
        for (int i = 0; i < 4; i++) {
            Step4(); //支持变化 ==> 虚函数的多态调用
        }
        Step5();
    }
    virtual ~Library() {}
    
  protected:
    void Step1() {
        //稳定
        cout << "Step1" << endl;
    }
    void Step3() {
        //稳定
        cout << "Step3" << endl;
    }
    void Step5() {
        //稳定
        cout << "Step5" << endl;
    }
    virtual bool Step2() = 0; //变化
    virtual void Step4() = 0; //变化
};
#include "Library.h"
#include 
using namespace std;

//应用程序开发人员
class Application : public Library {
  protected:
    virtual bool Step2() {
        //... 子类重写实现
        cout << "override Step2" << endl;
        return true;
    }
    virtual void Step4() {
        //... 子类重写实现
        cout << "override Step4" << endl;
    }
};

int main() {
    Library *pLib = new Application();
    pLib->Run();
    delete pLib;
}

策略模式 (Strategy)

作者在策略模式讲解过程中举了一个各国货币汇率换算问题,如果在Calculate函数中使用if else来枚举,当增加另外一个国家时,需要修改Calculate函数,显得很不优雅,可以使用多态来设计这个代码。


TaxStrategy是一个基类,定义了纯虚函数Calculate负责计算汇率,需要让各个国家派生类来实现。


对于每个国家,定义一个类继承TaxStrategy,并且实现Calculate函数。


在各国家类xxTax的创建中,使用工厂方法,策略模式拥有着更好的拓展性,并且能够节约计算资源。


class TaxStrategy{
public:
    virtual double Calculate(const Context& context)=0;
    virtual ~TaxStrategy(){}
};

class CNTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class USTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class DETax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class FRTax : public TaxStrategy{
public:
	virtual double Calculate(const Context& context){
		//.........
	}
};


class SalesOrder{
private:
    TaxStrategy* strategy;

public:
    SalesOrder(StrategyFactory* strategyFactory){
        this->strategy = strategyFactory->NewStrategy();
    }
    ~SalesOrder(){
        delete this->strategy;
    }
    public double CalculateTax(){
        //...
        Context context();
        double val = strategy->Calculate(context);
        //...
    }
    
};
观察者模式 (Observer)

模式定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。


作者举了一个文件分割器的例子来讲解观察者模式,MainForm代表一个文件分割器界面,可以输入文件路径和文件分割个数。


此时我们需要给MainForm添加若干个进度显示器,此时使用抽象通知机制而不是具体通知控件,如果MainForm选择传具体通知控件给FileSplitter,则FileSplitter依赖于MainForm


一种更好的方式就是定义一个抽象的IProgress代表抽象通知机制,MainForm继承FileSplitterIProgress接口,并实现IProgress中显示进度的方法。


class IProgress{
public:
	virtual void DoProgress(float value)=0;
	virtual ~IProgress() {}
};

class FileSplitter {
	string m_filePath;
	int m_fileNumber;
	List<IProgress*>  m_iprogressList; // 抽象通知机制,支持多个观察者

public:
	FileSplitter(const string& filePath, int fileNumber) :
		m_filePath(filePath), 
		m_fileNumber(fileNumber) {}
	void split(){
		// 1.读取大文件
		// 2.分批次向小文件中写入
		for (int i = 0; i < m_fileNumber; i++){
			//...
			float progressValue = m_fileNumber;
			progressValue = (i + 1) / progressValue;
			onProgress(progressValue);//发送通知
		}
	}
	void addIProgress(IProgress* iprogress){
		m_iprogressList.push_back(iprogress);
	}
	void removeIProgress(IProgress* iprogress){
		m_iprogressList.remove(iprogress);
	}

protected:
	virtual void onProgress(float value){
		List<IProgress*>::iterator itor=m_iprogressList.begin();
		while (itor != m_iprogressList.end() ) {
			(*itor)->DoProgress(value); //更新进度条
			itor++;
		}
	}
};
class MainForm : public Form, public IProgress {
	TextBox* txtFilePath;
	TextBox* txtFileNumber;
	ProgressBar* progressBar;
	
public:
	void Button1_Click() {
		string filePath = txtFilePath->getText();
		int number = atoi(txtFileNumber->getText().c_str());
		ConsoleNotifier cn;
		FileSplitter splitter(filePath, number);
		splitter.addIProgress(this);
		splitter.addIProgress(&cn);
		splitter.split();
		splitter.removeIProgress(this);
	}
	virtual void DoProgress(float value){
		progressBar->setValue(value);
	}
};

class ConsoleNotifier : public IProgress {
public:
	virtual void DoProgress(float value){
		cout << ".";
	}
};
装饰器 (Decorator)

动态(组合)地给一个对象增加一些额外的职责。


就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。


作者在装饰器设计模式中举了文件系统作为例子,在一般的设计中,FileSream、NetworkStream、MemoryStream继承Stream,在下一层中,CryptoFileStream、BufferedFileStream、CryptoBufferedFileStream分别继承FileStream,这就出现了一个问题。


前者可以认为是继承,后者则更像组合而不是继承。


我们可以用后图所示的结构实现这个系统,左边继承右边组合。


这就很优雅,很大程度上避免了代码的重复。


解决了原先仅靠继承方法的“子类急剧膨胀,充斥着 大量重复代码”的问题,使得原本的编译时装配升格为运行时装配。




//业务 *** 作
class Stream{
public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    virtual ~Stream(){}
};

//主体类
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //读文件流
    }
    virtual void Seek(int position){
        //定位文件流
    }
    virtual void Write(char data){
        //写文件流
    }
};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //读网络流
    }
    virtual void Seek(int position){
        //定位网络流
    }
    virtual void Write(char data){
        //写网络流
    }
};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //读内存流
    }
    virtual void Seek(int position){
        //定位内存流
    }
    virtual void Write(char data){
        //写内存流
    }
};

//扩展 *** 作
// 由于两个子类有相同的成员Stream*,所以这个成员要往上提
DecoratorStream: public Stream {
protected:
    Stream* stream;//...
    DecoratorStream(Stream * stm):stream(stm) { }
};

class CryptoStream: public DecoratorStream {
public:
    CryptoStream(Stream* stm):DecoratorStream(stm) { }
    virtual char Read(int number){
        //额外的加密 *** 作...
        stream->Read(number);//读文件流
    }
    virtual void Seek(int position){
        //额外的加密 *** 作...
        stream::Seek(position);//定位文件流
        //额外的加密 *** 作...
    }
    virtual void Write(byte data){
        //额外的加密 *** 作...
        stream::Write(data);//写文件流
        //额外的加密 *** 作...
    }
};

class BufferedStream : public DecoratorStream{
    Stream* stream;//...
public:
    BufferedStream(Stream* stm):DecoratorStream(stm) { }
    //...
};

void Process() {
    //运行时装配
    FileStream* s1=new FileStream();
    CryptoStream* s2=new CryptoStream(s1);
    BufferedStream* s3=new BufferedStream(s1);
    BufferedStream* s4=new BufferedStream(s2);
}
桥模式 (Bridge)

由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个纬度的变化。


将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。


作者在桥模式中举了同时拥有业务和平台两个维度扩展的例子,作者在重构过程中将Messager拆成了MessagerMessagerImp两个基类,因为作者认为有关业务跟平台的函数放在一个类中不利于扩展,此时Messager中有MessagerImp成员,属于组合。


对于业务的扩展,作者令MessagerLiteMessagerPerfect分别继承Messager;对于平台的扩展,作者令PCMessagerImpMobileMessagerImp分别继承MessagerImp


在运行时,将两个维度装配。


实现了代码的复用,实现了运行时装配。


class Messager{
protected:
     MessagerImp* messagerImp;//...
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;
    virtual ~Messager(){}
};

// 不同的变化方向(业务和平台),所以分为两个类
class MessagerImp{
public:
    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;
    
    virtual MessagerImp(){}
};

//平台实现 n
class PCMessagerImp : public MessagerImp{
public:
    virtual void PlaySound(){
        //**********
    }
    virtual void DrawShape(){
        //**********
    }
    virtual void WriteText(){
        //**********
    }
    virtual void Connect(){
        //**********
    }
};

class MobileMessagerImp : public MessagerImp{
public:
    virtual void PlaySound(){
        //==========
    }
    virtual void DrawShape(){
        //==========
    }
    virtual void WriteText(){
        //==========
    }
    virtual void Connect(){
        //==========
    }
};

//业务抽象 m
//类的数目:1+n+m
class MessagerLite :public Messager {
public:
    virtual void Login(string username, string password){
        
        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){
        
        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        
        messagerImp->DrawShape();
        //........
    }
};

class MessagerPerfect  :public Messager {
public:
    virtual void Login(string username, string password){
        messagerImp->PlaySound();
        //********
        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){
        messagerImp->PlaySound();
        //********
        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){
        messagerImp->PlaySound();
        //********
        messagerImp->DrawShape();
        //........
    }
};
void Process(){
    //运行时装配
    MessagerImp* mImp=new PCMessagerImp();
    Messager *m =new Messager(mImp);
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/564921.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-06
下一篇 2022-04-06

发表评论

登录后才能评论

评论列表(0条)

保存