Observer(观察者)

1. 意图

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

2. 动机

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

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

3. 示例

假设我们需要实现一个文件分割器,在split函数里面实现大文件的分割。

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++) {
                        //...
        }
    }
};

我们新建一个窗口,定义在一个按钮被点击时开始使用文件分割器类分割文件。

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();
    }
};

如果我们想在窗口中添加一个进度条,那么我们需要在窗口类和文件分割器类都中添加一个类型为ProgressBar*的进度条成员,并向文件分割器类的构造函数添加构造这个进度条的方法,修改split函数使其支持显示进度条。

这么做违背了依赖倒置原则,ProgressBar进度条属于实现细节,后期如需更换进度条为其它组件需要再次修改这些代码。我们可以使用一种抽象的方式表达ProgressBar进度条这样一种通知,通过定义一个接口来决定通知的形式。

class IProgress{
public:
    virtual void DoProgress(float value) = 0;
    virtual ~IProgress() {}
};
 
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;
            m_iprogress->DoProgress(progressValue); // 更新进度条
        }
    }
};

然后我们再让窗口类继承这个接口,重写DoProgress函数,并将this指针传递给分割器的构造函数。

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());
        FileSplitter splitter(filePath, number, this);
        splitter.split();
    }
    
    virtual void DoProgress(float value) {
        progressBar->setValue(value);
    }
};

我们可以继续修改分割器类,使其可以通知多个组件。首先,我们创建元素类型为IProgress*的m_iprogressList列表。其次,我们添加加入和删除通知机制的函数。最后,将通知行为代码抽象为onProgress函数,每次都通知列表中的所有元素。

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.add(iprogress);
    }
 
    void removeIProgress(IProgress* iprogress){
        m_iprogressList.remove(iprogress);
    }
 
protected:
    virtual void onProgress(float value){
        for (auto item : m_iprogressList) {
                item->DoProgress(value);
        }
    }
};

这样我们的分割器就可以支持更新多个组件。窗口类也需做对应的修改。

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

我们也可以将addIProgress、removeIProgress和onProgress函数以及m_iprogressList成员放到基类中,这里就不给出代码示例了。

4. 结构

5. 定义

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

专属学习链接:https://xxetb.xetslk.com/s/32bi94

6. 要点总结

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

目标(被观察者)发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。

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

Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值