定義
觀察者模式定義了對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。
這是定義,看不懂就看不懂吧,我接下來舉個例子慢慢說
為什么我們需要觀察者模式
我們看一個很簡單的需求,現在要你在游戲中加入成就系統,在物體墜落1000米的時候給玩家發一個成就勛章,你要這么做?
最直觀的方法就是,在游戲的物理系統那一部分中,加入這么一段代碼:
void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){if (surface.height - entity.height > 1000){//解鎖成就unlockFallOffBridge();}}
}
咋一看是不是還行?就加了幾行而已。
那么如果我還要求你播放墜落音效呢?是不是還得這樣寫:
void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){if (surface.height - entity.height > 1000){//解鎖成就unlockFallOffBridge();//播放音效playfallmusic();}}
}
這樣看也還行,那如果組長讓你根據物體撞擊不同的地面,播放不同的地面音效,那這段代碼是不是又得膨脹了:
void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){if (surface.height - entity.height > 1000){//解鎖成就unlockFallOffBridge();//播放音效if (hitground){playhitgroundmusic();}if (hitwater){playhitwatermusic();}//.....}}
}
要知道,這可是在你的游戲的物理引擎中,我們并不想看到在處理撞擊代碼的線性代數時, 有出現關于成就系統,音效系統的調用是不?我們喜歡的是,照舊,讓關注游戲一部分的所有代碼集成到一塊。我們想要解耦物理系統和這些不相關的東西。
這就是觀察者模式出現的原因。 這讓代碼宣稱有趣的事情發生了,而不必關心到底是誰接受了通知。
一旦你使用了觀察者模式,你的代碼就會變成這樣:
void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){notify(entity, EVENT_START_FALL);}
}
是不是簡潔了很多很多?比剛才那一大堆丑陋的代碼好看多了。
觀察者模式做的就是聲稱,“額,我不知道有誰感興趣,但是這個東西剛剛掉下去了。做你想做的事吧。”
可能有人會說,誒,這也沒有完全解耦啊。的確,物理引擎確實決定了要發送什么通知,所以這并沒有完全解耦。但在架構這個領域,通常只能讓系統變得更好,而不是完美。
如何構建觀察者模式?
最傳統的構建方式就是這樣,使用對象模式構建觀察者
我們先寫一個基礎的觀察者抽象基類
class Observer
{
public:virtual ~Observer() {}virtual void onNotify(const Entity& entity, Event event) = 0;
};
然后讓我們的成就系統和音效系統等想成為觀察者的系統都繼承這個基類:
class Achievements : public Observer
{
public:virtual void onNotify(const Entity& entity, Event event){switch (event){case EVENT_ENTITY_FELL:if (entity.isHero() && heroIsOnBridge_){unlock(ACHIEVEMENT_FELL_OFF_BRIDGE);}break;// 處理其他事件,更新heroIsOnBridge_變量……}}private:void unlock(Achievement achievement){// 如果還沒有解鎖,那就解鎖成就……}bool heroIsOnBridge_;
};
對于被觀察者,如物理系統中,我們只要讓它持有這個observer的指針就好了,一旦出現了某些事件,我們就給這些指針指向的observer發消息。
為了正式一點,讓所有可能的系統都成為被觀察者,我們寫一個叫subject的基類,讓所有想成為被觀察者的系統都可以繼承這個基類來成為被觀察者。
class Subject
{
public:void addObserver(Observer* observer){// 添加到數組中……}void removeObserver(Observer* observer){// 從數組中移除……}void removeObserver(Observer* observer){// 從數組中移除……}
protected:void notify(const Entity& entity, Event event){for (int i = 0; i < numObservers_; i++){observers_[i]->onNotify(entity, event);}}private:Observer* observers_[MAX_OBSERVERS];int numObservers_;
};
我們可以看見,這里寫了一個觀察者數組,存了許多觀察者的指針,這是因為大部分情況下,被觀察者可能會有好多個觀察者觀察著它。然后我們也寫了一些方法來增刪這個數組。
然后就是面向對象的東西了,我們讓物理系統繼承這個基類
class Physics : public Subject
{
public:void updateEntity(Entity& entity);
};
現在,當物理引擎做了些值得關注的事情,它調用notify(),就像之前的例子。 它遍歷了觀察者列表,通知所有觀察者。
恭喜你已經掌握了如何寫一個觀察者模式,你所看到的就是一個觀察者模式的全部。現在來回顧一下定義:
觀察者模式定義了對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。
是不是有點明白了?
**
觀察者模式的使用場合
**
當一個抽象模式有兩個方面,其中一個方面依賴于另一個方面,需要將這兩個方面分別封裝到獨立的對象中,彼此獨立地改變和復用的時候。
當一個系統中一個對象的改變需要同時改變其他對象內容,但是又不知道待改變的對象到底有多少個的時候。
當一個對象的改變必須通知其他對象作出相應的變化,但是不能確定通知的對象是誰的時候。
觀察者模式的缺點:
- 由于觀察者模式調用了一些虛方法,終究會比靜態調用慢一些。
- 觀察者模式是同步的。 被觀察者直接調用了觀察者,這意味著直到所有觀察者的通知方法返回后, 被觀察者才會繼續自己的工作。觀察者會阻塞被觀察者的運行。
- 由于被觀察者維護了一個數組來存儲觀察者指針,在實際情況中一般會用動態數組而不是這次例子中的靜態數組。這樣就會做出太多的動態分配。解決方法還是有的,那就是使用鏈表而不是數組來存儲觀察者指針(反正你都得遍歷發通知,這倆差不多)。
原文鏈接:https://gpp.tkchu.me/observer.html