第5节 观察者模式

一、模式动机

  1. 在软件构建中,我们需要为某些对象构建一种"通知依赖"关系,一个对象(目标对象)状态改变时,所有依赖对象(观察者)都将得到通知;
  2. 使用OOP(面向对象编程)技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系,从而实现软件体系接口的松耦合;

二、程序示例

2.1 使用设计模式前

这里,我们设计一个文件分割的例子,虽然现在用的不多,因为现在的存储介质容量都相对很大,但是作为一个例子来说明设计模式的解耦思维.

#include 
#include 
#include 

using namespace std;

// 声明一个文二建分割器Class
class FileSplitter
{
public:
    // 使用初始化列表初始化私有成员变量
    FileSplitter(const std::string& filePath,int fileNumber):m_filePath(filePath),m_fileNumber(fileNumber){}
public:
    void split()
    {
	// 1. 读取大文件
	// 2. 分批次从向小文件中写入
	for (int i = 0; i < m_fileNumber; i++)
	{
	    // ... 文件写入工作
	}
    }
private:
    std::string m_filePath;
    int m_fileNumber;
};

// 声明一个窗体类,继承自窗体基类,类似Qt或MFC,其中Form类是伪代码假设
class Form{};
class TextBox{};
class MainForm:public Form                
{
public:
    void Button_Click()                                       // 一个点击事件方法
    {
	std::string filePath = txtFilePath->getText();
	int num = atoi(txtFileNumber->getText.c_str());       // 文本转化为整型
	FileSplitter splitter(filePath,num);
	splitter.split();
    }
private:
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
};

int main(){
    MainForm main_window;                                     // 然后做点击操作
    rentrun 0;
}

到此,一个简单的文件分割窗口程序伪代码就写好了。如果现在用户提出一个需求:如果文件过大,文件分割可能持续较长时间,能否设置一个进度条,实时查看文件分割的进度?可能实现的解决方案:

  • 在MainForm里添加一个进度条,如ProgressBar* m_progressBar;
  • 然后在在split()方法for循环内添加:
if(m_progessBar!=nullptr) 
    m_progessBar->setValue((i+1)/(double)m_fileNumber*100);

2.2 问题思考

如果将来某天,用户提出需求,不在使用进度条了,要使用文本框或者控制台的方式怎么办?按照当前的设计方式,我们一定在代码迭代时需要同时修改MainForm和FileSplitter中的代码,这就不是运行时依赖了,而是典型的编译时依赖,这就不是一种稳定的代码设计方式.

从代码设计原则上看,上述代码违背了OOP八大设计原则的第一条:依赖倒置原则(DIP),即:

  • 高层模块(稳定)不应该依赖于底层(变化),二者都应该依赖于抽象;
  • 抽象不应该依赖细节实现变化,实现细节应该依赖于抽象;

总的来说,上述实现方式是编译时依赖而不是运行时依赖的,最完美的程序设计需要运行时依赖. 我们不能保证以后的进度通知时通过哪种具体的方式,甚至通知的数量,因此我们要在高层和底层之间增加一层抽象.

2.3 使用设计模式后

思路:其中ProgressBar体现了一种消息的通知,而不是具体的展现方式.

#include 
#include 
#include 

using namespace std;

// 使用OOP多态,在高层和底层之间增加一层抽象
class IProgress {
public:
    virtual void DoProgress(float value) = 0;           // 从一个具体的控件,到一个消息通知的抽象
    virtual ~IProgress(){}
};

class FileSplitter:public IProgress                     // 底层细节继承中间抽象层
{
public:
    virtual ~FileSplitter() {}
    // 使用初始化列表初始化私有成员变量
    FileSplitter(const std::string& filePath,int fileNumber):m_filePath(filePath),m_fileNumber(fileNumber){}
public:
    void split()
    {
	// 1. 读取大文件
	// 2. 分批次从向小文件中写入
	for (int i = 0; i < m_fileNumber; i++)
	{
	    // ... 文件写入工作
            if(m_progessBar!=nullptr) 
                this->DoProgress((i+1)/(double)m_fileNumber*100);
	}
    }

    void DoProgress(float value){ /* 消息通知的代码 */}                // 实现进度消息的通知

    // 使用容器添加多个观察者对象
    void addIProgress(IProgress* iprogress)
    {
	m_iprogressList.push_back(iprogress);
    }

    void removeIProgress(IProgress* iprogress)
    {
        m_iprogressList.remove(iprogress);
    }
protected:                                               // 使用容器管理多个容器
    virtual void onProgress(float value)
    {
	for (auto iter = m_iprogressList.begin; iter != m_iprogressList.end; iter++)
	    iter->DoProgress(value);
    }
private:
    std::string m_filePath;
    int m_fileNumber;
    IProgress* m_iprogress;                              // 使用抽象
    std::list<IProgress*> m_iprogressList;               // 使用容器改良,可以同时通知多个观察者 
};

// 界面窗体
class MainForm:public IProgress                            // 使用抽象后
{
public:
    void Button_Click()                                    // 一个点击事件方法
    {
	std::string filePath = txtFilePath->getText();
	int num = atoi(txtFileNumber->getText.c_str());    // 文本转化为整型
	FileSplitter splitter(filePath,num);
	splitter.split();
    }
public:
	// Override   通过继承的方式实现虚方法
	virtual void DoProgress(float value){
             // 实现具体消息通知的代码
        }
private:
	TextBox* txtFilePath;
	TextBox* txtFileNumber;
};
### 2.4 结果分析
看起来观察者模式与策略模式有点相似,但在死路上有本质的区别。策略模式强调扩展性,而观察者模式强调依赖倒置.

## 三、要点总结

 1. 使用OOP的抽象,观察者模式使得我们可以独立地改变目标和观察者,从而使两者之间的依赖关系达到松耦合;
 2. 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播; 
 3. 观察者自己决定是否需要订阅通知,目标对象对此一无所知;
 4. 观察者模式基于事件的UI框架中非常常用的设计模式,一个MVC模式的一个重要组成部分; 
 5. 非UI框架时,在回调函数中也使用非常多.

你可能感兴趣的:(C++,设计模式,c++,编程语言,设计模式)