C++设计模式——观察者模式Observer-Pattern

动机(Motivation)

  • 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对 象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
  • 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

模式定义

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

结构(Structure)

模式举例

在以前电脑和存储介质容量都很小的时候,会经常使用到文件分割器这种软件。举个例子,一部电影400M,只有一个U盘,64M的,要把它复制到另一台电脑,一次无法完成。就用文件分割器把400M分成几块,每块约60M,复制到之后,再它分割器把它合成原来的文件。我们如果想要看到文件分割器的进度,可以增加一个进度条功能,这个功能的实现也非常的简单,无非是大文件分批次向小文件写的时候记录一下,我们通常会写出下面的代码:

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++){
            //...
            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            m_progressBar->setValue(progressValue);//更新进度条
        }

    }
};

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

但这种设计不是很好,依赖实现细节,违背了设计原则的依赖倒置原则,高层模块不应该依赖于低层模块,二者都应该依赖于抽象。

Windows、Linux、 控制台下的进度条是不同的,我想让这个程序都能适应,显然要设计更多的具体通知控件,仅仅增加了新的通知控件,就需要对原有的FileSplitter类进行修改,这样的设计是很糟糕的。可以做进一步的抽象,既然有多个通知控件,就可以为这些控件之间抽象一个接口出来,用来取消FileSplitter和具体控件之间的依赖,于是有了观察者模式。

//通知接口
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 << ".";
    }
};

要点总结

  • 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
  • Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。

基本代码

#include <iostream>
#include <list>
#include <string>
using namespace std;

class Observer { // 抽象观察者,为所有具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫更新接口
public:
    virtual void Update() = 0;
};

class Subject { // 主题或抽象通知者,一般用一个抽象类或者接口实现
private:
    list<Observer* > observers;
public:
    void Attach(Observer* observer) { observers.push_back(observer); } // 增加观察者
    void Detach(Observer* observer) { observers.remove(observer); } // 移除观察者
    void Notify() { // 通知
        for (auto observer = observers.begin(); observer != observers.end(); observer++) {
            (*observer)->Update();
        }
    }
};

class ConcreteSubject : public Subject { // 具体主题或具体通知者,将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过的观察者发送通知
private:
    string subjectState;
public:
    string GetState() { return subjectState; }
    void SetState(string state) { subjectState = state; }
};

class ConcreteObserver : public Observer { // 具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态相协调
private:
    string name;
    ConcreteSubject* subject;
    string observerState;
public:
    ConcreteObserver(ConcreteSubject* s, string n) {
        subject = s;
        name = n;
    }
    void Update() {
        observerState = subject->GetState();
        cout << "observer: " << name << ", its new state is: " << observerState << endl;
    }
    string GetState() { return observerState; }
    void SetState(string state) { observerState = state; }
};

int main() {
    ConcreteSubject* s = new ConcreteSubject();
    s->SetState("ABC");
    ConcreteObserver* ox = new ConcreteObserver(s, "X");
    ConcreteObserver* oy = new ConcreteObserver(s, "Y");
    ConcreteObserver* oz = new ConcreteObserver(s, "Z");
    s->Attach(ox);
    s->Attach(oy);
    s->Attach(oz);
    s->Notify();
    // observer: X, its new state is: ABC
    // observer: Y, its new state is: ABC
    // observer: Z, its new state is: ABC
    s->SetState("XYZ");
    s->Notify();
    // observer: X, its new state is: XYZ
    // observer: Y, its new state is: XYZ
    // observer: Z, its new state is: XYZ
    delete ox;
    delete oy;
    delete oz;
    delete s;
    return 0;
}

 

posted @ 2020-03-13 14:44  王陸  阅读(599)  评论(0编辑  收藏  举报