第5節 觀察者模式

一、模式動機

  1. 在軟件構建中,我們需要爲某些對象構建一種"通知依賴"關係,一個對象(目標對象)狀態改變時,所有依賴對象(觀察者)都將得到通知;
  2. 使用OOP(面向對象編程)技術,可以將這種依賴關係弱化,並形成一種穩定的依賴關係,從而實現軟件體系接口的松耦合;

二、程序示例

2.1 使用設計模式前

這裏,我們設計一個文件分割的例子,雖然現在用的不多,因爲現在的存儲介質容量都相對很大,但是作爲一個例子來說明設計模式的解耦思維.

#include <stdio.h>
#include <string>
#include <list>

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 <stdio.h>
#include <string>
#include <list>

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框架時,在回調函數中也使用非常多.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章