C++ 设计模式 观察者模式

以下内容均来自GeekBand极客班C++ 设计模式课程(李建忠老师主讲)

完整代码见:https://download.csdn.net/download/qq_41605114/18881788

Observer

“组件协作”模式:

现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作模式”通过晚绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。

典型模式:

Template Method

Strategy

Observer/Event

动机(Motivation)

在软件构建过程中,我们需要为某些对象建立一种“通过依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。

GOF定义:

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

蓝色为稳定部分,橘黄色为变化部分

使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

一种通知机制。

下面我们以文件分割器为示例

结构化编程示例

下面是文件分割器:

class FileSplitter{
    string m_filePath;
    int m_fileNumber;
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){
            //...
        }
    }
};

下面是MainFrom主界面

class MainForm : public Form{
    TextBox * txtFilePath;
    TextBox * txtFileNumber;
public:
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());
        
        FileSplitter splitter(filePath,number);//创建分割文件对象
        
        splitter.split();
    }
};

现在要在文件分割的过程中提供一个进度条的功能

最简单和直观的做法如下:

class FileSplitter{
    string m_filePath;
    int m_fileNumber;
    
    ProgressBar * m_progressBar;//更新进度条
public:
    FileSplitter(const string & filePath,int fileNumber,ProgressBar * progressBar){
        m_filePath(filePath);
        m_fileNumber(fileNumber);
        m_progressBar(progressBar);
    }
    
    void split(){
        //1,读取大文件
        
        //2,分批次向小文件中写入
        for(int i = 0;i < m_fileNumber;++i){
            //...
            
            if(m_progressBar){
                m_progressBar->setValue( (i + 1)/m_fileNumber );//更新进度条
            }
            
        }
    }
};

class MainForm : public Form{
    TextBox * txtFilePath;
    TextBox * txtFileNumber;
    
    ProgressBar * m_progressBar;//更新进度条
    
public:
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());
        
        FileSplitter splitter(filePath,number,m_progressBar);//创建分割文件对象
        
        splitter.split();
    }
};

现在如果要求更改ProgressBar的表达形式,比如打点显示,直接百分比,那么以上代码修改的地方就不是一点两点

ProgressBar是一种“通知”的具体表现形式,FileSplitter类目前对于ProgressBar是一种紧耦合,如果现在需要更新通知机制,那么显然不符合依赖倒置原则。

观察者模式

现在增加一个抽象类:

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

class FileSplitter

class FileSplitter{
    string m_filePath;
    int m_fileNumber;

//    ProgressBar * m_progressBar;//具体通知控件
    IProgress * m_iprogress;//抽象通知机制
public:
    FileSplitter(const string & filePath,int fileNumber,IProgress * iprogress){
        m_filePath(filePath);
        m_fileNumber(fileNumber);
        m_iprogress(iprogress);
    }

    void split(){
        //1,读取大文件

        //2,分批次向小文件中写入
        for(int i = 0;i < m_fileNumber;++i){
            //...
            float progressValue = m_fileNumber;
            progressValue = (i + 1)/progressValue;
            onProgress(progressValue);//抽象通知机制
        }
    }
protected:
    //重构,考虑子类是否会重写这个部分的功能
   virtual  void onProgress(float value){
        if(m_iprogress){
            m_iprogress->DoProgress( value );//抽象通知机制
        }
    }

};

FileSplitter类目前是松耦合的状态,has 一个抽象类,这个抽象类中进度通知部分,可以交给具体的情况去完成。

下面是界面类:

class MainForm : public Form , public IProgress{
    //多继承,一个父类,剩下的都是接口,是可以的
    TextBox * txtFilePath;
    TextBox * txtFileNumber;

    ProgressBar * m_progressBar;

public:
    virtual void DoProgress(float value){
        m_progressBar->setValue(value);
    }
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());

        FileSplitter splitter(filePath,number,m_progressBar);//创建分割文件对象

        splitter.split();
    }
};

进度条具体的实现,直接放置MainForm中进行实现。

那么上面的代码是单一通知机制,如果要有多个通知呢?上面的例子中,只有MainForm一个观察者,要完成上面的要求也很简单,直接使用一个容器即可。

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

class FileSplitter{
private:
    string m_filePath;
    int m_fileNumber;

//    ProgressBar * m_progressBar;//具体通知控件
    IProgress * m_iprogress;//抽象通知机制 支持一个观察者
    list<IProgress *> m_iprogressList;//抽象通知机制 支持多个观察者
public:
    //单个观察者的构造函数
    FileSplitter(const string & filePath,int fileNumber,IProgress * iprogress){
        m_filePath(filePath);
        m_fileNumber(fileNumber);
        m_iprogress(iprogress);
    }
    //多个观察者时,需要增加接口函数
    FileSplitter(const string & filePath,int fileNumber){
        m_filePath(filePath);
        m_fileNumber(fileNumber);
    }
    void addIprogress(IProgress * iprogress){
        m_iprogressList.push_back(iprogress);
    }
    void removeIprogress(IProgress * iprogress){
        m_iprogressList.remove(iprogress);
    }
    void split(){
        //1,读取大文件

        //2,分批次向小文件中写入
        for(int i = 0;i < m_fileNumber;++i){
            //...
            float progressValue = m_fileNumber;
            progressValue = (i + 1)/progressValue;
            onProgress(progressValue);//抽象通知机制
        }
    }
protected:
    //重构,考虑子类是否会重写这个部分的功能
   virtual  void onProgress(float value){
        //单个观察者
        if(m_iprogress){
            m_iprogress->DoProgress( value );//抽象通知机制
        }
        //多个观察者
        for(auto item : m_iprogressList){
            item->DoProgress( 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 * m_progressBar;

public:
    virtual void DoProgress(float value){
        m_progressBar->setValue(value);
    }
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());

        //单个观察者
        FileSplitter splitter(filePath,number,this);//创建分割文件对象
        //多个观察者
        splitter.addIprogress(this);
        splitter.addIprogress(&other);

        splitter.split();
    }
};
//假设下面是第二个观察者
class ConsoleNotifier : public IProgress{
    
};

要点总结

使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系转化为松耦合。

目标发送通知时,无需指定观察者,通知会自动传播

观察者自己决定是否需要订阅通知,目标对象对此一无所知

Observer模式是基于时间的UI矿建中非常常用的设计模式,也是MVC模式的一个重要组成部分

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值