C++設計模式之觀察者模式(observer)(行爲型)

一 定義

觀察者模式:定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。

觀察者模式又叫做發佈-訂閱(Publish-Subscribe)模式、模型-視圖(Model-View)模式、源-監聽器(Source-Listener)模式。

Observer模式應該可以說是應用最多、影響最廣的模式之一,因爲Observer的一個實例Model/View/Control(MVC)結構在系統開發架構設計中有着很重要的地位和意義,MVC實現了業務邏輯和表示層的解耦。

 

圖2

觀測模式允許一個對象關注其他對象的狀態,並且,觀測模式還爲被觀測者提供了一種觀測結構,或者說是一個主體和一個客體。主體,也就是被觀測者,可以用來聯繫所有的觀測它的觀測者。客體,也就是觀測者,用來接受主體狀態的改變 觀測就是一個可被觀測的類(也就是主題)與一個或多個觀測它的類(也就是客體)的協作。不論什麼時候,當被觀測對象的狀態變化時,所有註冊過的觀測者都會得到通知。
觀測模式將被觀測者(主體)從觀測者(客體)種分離出來。這樣,每個觀測者都可以根據主體的變化分別採取各自的操作。(觀測模式和Publish/Subscribe模式一樣,也是一種有效描述對象間相互作用的模式。)觀測模式靈活而且功能強大。對於被觀測者來說,那些查詢哪些類需要自己的狀態信息和每次使用那些狀態信息的額外資源開銷已經不存在了。另外,一個觀測者可以在任何合適的時候進行註冊和取消註冊。你也可以定義多個具體的觀測類,以便在實際應用中執行不同的操作。
將一個系統分割成一系列相互協作的類有一個常見的副作用:需要維護相關對象間的一致性。我們不希望爲了維持一致性而使各類緊密耦合,因爲這樣降低了它們的可重用性。

觀察者模式所做的工作其實就是在解除耦合,讓耦合的雙方都依賴於抽象,而不是依賴於具體。從而使得各自的變化都不會影響另一邊的變化。

二 ULM圖

角色:

● Subject(目標):目標又稱爲主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標可以接受任意數量的觀察者來觀察,它提供一系列方法來增加和刪除觀察者對象,同時它定義了通知方法notify()。目標類可以是接口,也可以是抽象類或具體類。 

● ConcreteSubject(具體目標):具體目標是目標類的子類,通常它包含有經常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象業務邏輯方法(如果有的話)。如果無須擴展目標類,則具體目標類可以省略。 

● Observer(觀察者):觀察者將對觀察目標的改變做出反應,觀察者一般定義爲接口,該接口聲明瞭更新數據的方法collect(),因此又稱爲抽象觀察者。 

● ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態需要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的collect()方法。

一共四個類,兩個接口,兩個接口實現類,被觀察者方法參數引用的是觀察者對象。

觀察者只定義一個自己的行爲。具體觀察者重寫觀察者的行爲後還提供了構造方法爲客戶端調用。具體被觀察者,這個類有些複雜,簡單的說就是把外界消息通過該類的方法調用 notifyObserver() 方法給提前準備好的Observer (觀察者) 接口中的方法去發送。

模式優點:

  1. 觀察者模式可以實現表示層和數據邏輯層的分離,並定義了穩定的消息更新傳遞機制,抽象了更新接口,使得可以有各種各樣不同的表示層作爲具體觀察者角色。
  2. 在觀察目標和觀察者之間建立一個抽象的耦合 :一個目標所知道的僅僅是它有一系列觀察者 , 每個都符合抽象的Observer類的簡單接口。目標不知道任何一個觀察者屬於哪一個具體的類。這樣目標和觀察者之間的耦合是抽象的和最小的。因爲目標和觀察者不是緊密耦合的, 它們可以屬於一個系統中的不同抽象層次。一個處於較低層次的目標對象可與一個處於較高層次的觀察者通信並通知它 , 這樣就保持了系統層次的完整。如果目標和觀察者混在一塊 , 那麼得到的對象要麼橫貫兩個層次 (違反了層次性), 要麼必須放在這兩層的某一層中(這可能會損害層次抽象)。
  3. 支持廣播通信 :不像通常的請求, 目標發送的通知不需指定它的接收者。通知被自動廣播給所有已向該目標對象登記的有關對象。目標對象並不關心到底有多少對象對自己感興趣 ;它唯一的責任就是通知它的各觀察者。這給了你在任何時刻增加和刪除觀察者的自由。處理還是忽略一個通知取決於觀察者。
  4. 觀察者模式符合“開閉原則”的要求。

模式缺點:

  1. 如果一個觀察目標對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
  2. 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
  3. 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
  4. 意外的更新 因爲一個觀察者並不知道其它觀察者的存在 , 它可能對改變目標的最終代價一無所知。在目標上一個看似無害的的操作可能會引起一系列對觀察者以及依賴於這些觀察者的那些對象的更新。此外 , 如果依賴準則的定義或維護不當,常常會引起錯誤的更新 , 這種錯誤通常很難捕捉。

適用場景:

  (1)當一個對象的改變需要同時改變其他對象的時候;

(2)而且不知道具體有多少對象有待改變時,應該考慮使用觀察者模式;

(3)當一個抽象模型有兩個方面,其中一方面依賴於另一方面,這時用觀察者模式可以將這兩者封裝在獨立的對象中使他們各自獨立地改變和複用。

三 實例

1. 博客

當博主發表新文章的時候,即博主狀態發生了改變,那些訂閱的讀者就會收到通知,然後進行相應的動作,比如去看文章,或者收藏起來。博主與讀者之間存在種一對多的依賴關係。下面給出相應的UML圖設計。 

 可以看到博客類中有一個觀察者鏈表(即訂閱者),當博客的狀態發生變化時,通過Notify成員函數通知所有的觀察者,告訴他們博客的狀態更新了。而觀察者通過Update成員函數獲取博客的狀態信息。

#include <iostream>
#include <string>
#include <list>
#include <memory>

//observer
class Observer
{
public:
	Observer() = default;
	virtual ~Observer() = default;
	virtual void Update() = 0;
};

//subject
class Blog
{
public:
	Blog() = default;
	virtual ~Blog() = default;
	void Attach(std::shared_ptr<Observer> observer)
	{ 
		observers.push_back(observer); 
	}
	void Remove(std::shared_ptr<Observer> observer)
	{ 
		observers.remove(observer); 
	}
	void Notify()
	{
		for (auto iter = observers.begin(); iter != observers.end(); iter++)
		{
			(*iter)->Update();
		}
	}
	virtual void SetStatus(const std::string& status)
	{ 
		this->status = status;
	}
	virtual std::string GetStatus() 
	{ 
		return status; 
	}
private:
	std::list<std::shared_ptr<Observer>> observers; //觀察者鏈表
protected:
	std::string status; //狀態
};

//concrete subject
class BlogCSDN : public Blog
{
public:
	BlogCSDN(const std::string& bloggerName) : bloggerName{ bloggerName }{}
	~BlogCSDN() = default;

	void SetStatus(const std::string& state) 
	{ 
		status = "CSDN通知 : " + bloggerName + state;
	} 
	std::string GetStatus() 
	{
		return status; 
	}
private:
	std::string bloggerName;
};

//concreteObserver
class ObserverBlog : public Observer
{
private:
	std::string observerName;
	std::shared_ptr<Blog> blog;   //觀察的博客,當然以鏈表形式更好,就可以觀察多個博客
public:
	ObserverBlog(const std::string& observerName, std::shared_ptr<Blog> blog)
		: observerName{ observerName }
		, blog{ blog }
	{}
	~ObserverBlog() = default;
	void Update()
	{
		std::string status = blog->GetStatus();//收到更新通知後,閱讀關注博客更新的內容等操作
		std::cout << observerName << status << std::endl;
	}
};

int main()
{
	auto blog = std::make_shared<BlogCSDN>("秋雲");//創建一個CSDN博客的博主---秋雲
	auto observer = std::make_shared<ObserverBlog>("子之", blog);//子之關注了秋雲的博客
	blog->Attach(observer);//把子之放到了博主秋雲的粉絲集合中
	blog->SetStatus("發表設計模式——觀察者模式");//SetStatus是博主更新了內容。
	blog->Notify();

	return 0;
}

 

2. 貓狗老鼠

貓"喵喵"叫了一聲,狗也跟着"汪汪"叫,老鼠聽到貓叫聲後跑了。

#include <iostream>
#include <vector>

//Observer
class MyObserver 
{
public:
	virtual void response() = 0;
};

//Subject
class MySubject 
{
public:

	void attach(MyObserver* observer) 
	{
		observers.push_back(observer);
	}

	void detach(MyObserver* observer) 
	{
		for (auto it = observers.begin(); it != observers.end(); it++) 
		{
			if (*it == observer) {
				observers.erase(it);
				break;
			}
		}
	}
protected:
	std::vector<MyObserver*> observers;
};

//concrete subject
class Cat : public MySubject 
{
public:
	void cry() 
	{
		std::cout << "貓喵喵叫!" << std::endl;
		for (auto obs : observers) 
		{
			obs->response();
		}
	}
};

//concrete observer
class Mouse : public MyObserver 
{
public:
	void response() 
	{
		std::cout << "老鼠逃跑!" << std::endl;
	}
};

//concrete observer
class Dog : public MyObserver 
{
public:
	void response() 
	{
		std::cout << "狗汪汪叫!" << std::endl;
	}
};


int main(void) 
{
	Cat cat;
	Mouse m1, m2;
	Dog dog;

	cat.attach(&m1);
	cat.attach(&m2);
	cat.attach(&dog);

	cat.cry();

	return 0;
}

運行結果如下:

 

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