玩命加载中 . . .

5-观察者模式


Observer

有时候需要为某些对象建立“通知依赖关系”,一个对象(目标对象)的状态发生变化,所有的依赖对象(观察者对象)都将得到通知

实现一个文件分割器

// MainForm.cpp
class MainForm : public Form {
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
    ProgressBar* progressBar;

public:
    void Button1_Click() {

        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());

        FileSplitter splitter(filePath, number, progressBar);

        splitter.split();

    }
};

如果想要显示文件分割的进度条,就需要在文件分割的过程中更新进度条的数据,调用相应方法

// FileSplitter.cpp
class FileSplitter {
    string m_filePath;
    int m_fileNumber;
    ProgressBar* m_progressBar; // 依赖于MainForm的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++){
            //...
            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            m_progressBar->setValue(progressValue);
        }

    }
};

改进

这里依赖于MainForm,如果MainForm要修改progressBarFileSplitter也得修改,所以应该依赖于抽象,而不是实体,所以声明一个抽象类,定义了进度显示的接口

class IProgress{
public:
    virtual void DoProgress(float value)=0;	// 抽象类的接口
    virtual ~IProgress(){}
};

然后让MainForm继承这个抽象类,实现接口

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 << ".";
    }
};

FileSplitter里面可以添加多个进度显示对象(观察者),进度发生变化就通知这些观察者,也就是调用观察者对应的接口实现函数

list里面放的是抽象类的指针,这样就可以利用多态调用不同子类的虚函数,这样就可以支持多种进度显示,而不是和特定的ProgressBar绑定

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++;
        }
    }
};

这样FileSplitter依赖于IProgress,只需要调用其接口就可以了,不需要知道具体是怎么实现的,具体的接口实现是在MainForm里面,如果要修改接口就直接在MainForm里改就行了,FileSplitter不用动

模式定义

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

  • 使用面向对象的抽象,Observer 模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系保持松耦合
  • 目标发送通知时,无需指定观察者,通知会自动传播
  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知
  • Observer 模式是基于事件的 UI 框架中非常常用的设计模式

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录