日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

趣谈设计模式 | 观察者模式(Observer) :消息的发布与订阅

發(fā)布時(shí)間:2024/4/11 asp.net 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 趣谈设计模式 | 观察者模式(Observer) :消息的发布与订阅 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 案例:文章推送
  • 觀察者模式
    • 觀察者模式的運(yùn)作流程
    • 觀察者模式解決的問題
  • 觀察者模式大顯身手
  • 總結(jié)
    • 要點(diǎn)
    • 應(yīng)用場(chǎng)景
    • 生產(chǎn)者-消費(fèi)者模型 VS 觀察者模式
  • 完整代碼及文檔


案例:文章推送

假設(shè)我是一個(gè)科幻小說愛好者,我維護(hù)著一個(gè)叫做ScienceFictionPusher的公眾號(hào),定期向豆瓣、知乎等平臺(tái)推送那些我覺得有趣的科幻小說,于是為了方便管理,我的推送程序是這樣的邏輯

class ScienceFictionPusher { public://推送內(nèi)容void newPush(){//分別向各大平臺(tái)推送內(nèi)容_zhihu->update(_url, _title, _desc);_douban->update(_url, _title, _desc);}//設(shè)置新的內(nèi)容void setNewFiction(const std::string& url, const std::string& title, const std::string& desc){_url = url;_title = title;-_desc = desc;newPush();}private:std::string _url; //小說鏈接std::string _title; //小說名std::string _desc; //小說簡(jiǎn)介Douban* _douban;Zhihu* _zhihu; };

上面這種實(shí)現(xiàn)方式咋一看沒什么問題,甚至在某些地方處理的還不錯(cuò),因?yàn)槲覀儗?nèi)容的更新從平臺(tái)主動(dòng)的拉取變?yōu)榱斯娞?hào)的主動(dòng)推送,大大減少了空轉(zhuǎn)時(shí)間。因此,我們將代碼投入使用

隨著粉絲越來越多,公眾號(hào)的名氣也越來越大,于是乎越來越多的平臺(tái)開始邀請(qǐng)我的專欄入駐,但是此時(shí)就出現(xiàn)了問題

如果采用上面這種模式的話,當(dāng)有大量的平臺(tái)時(shí),代碼會(huì)是這樣的,存在大量的冗余,可讀性也極差

void newPush() {//分別向各大平臺(tái)推送內(nèi)容_zhihu->update(_url, _title, _desc);_douban->update(_url, _title, _desc);_wechat->update(_url, _title, _desc);_uc->update(_url, _title, _desc);_tiktok->update(_url, _title, _desc);_bilibili->update(_url, _title, _desc);_baidu->update(_url, _title, _desc);_csdn->update(_url, _title, _desc);...........................}

由于公眾號(hào)的經(jīng)營(yíng)也存在波動(dòng),當(dāng)流量大的時(shí)候我們會(huì)有新增的平臺(tái),當(dāng)某個(gè)平臺(tái)流量小的時(shí)候我們也不會(huì)再去維護(hù),所以平臺(tái)的數(shù)量是時(shí)刻變化的,那這樣的代碼就意味著我們需要時(shí)刻去程序中修改,無法動(dòng)態(tài)的增加、刪除,效率極低。

那有什么好的解決方法嗎?這就到了 觀察者模式出場(chǎng)的時(shí)候


觀察者模式

觀察者模式也叫做發(fā)布訂閱模式,它定義了對(duì)象之間的一對(duì)多依賴,當(dāng)一個(gè)對(duì)象改變狀態(tài)的時(shí)候,它的所有依賴著都會(huì)收到通知并自動(dòng)更新。

為了方便舉例,這里我們將發(fā)布內(nèi)容的對(duì)象稱為主題接收內(nèi)容的對(duì)象稱為觀察者

觀察者模式的運(yùn)作流程

此時(shí)對(duì)象C也想要獲取內(nèi)容,所以它告訴主題他想要注冊(cè)成為觀察者


由于主題發(fā)布的內(nèi)容質(zhì)量逐漸降低,對(duì)象A不再需要訂閱,此時(shí)它請(qǐng)求注銷主題


從上面我們可以看到,主題主要做了三件事,注冊(cè)、刪除、通知觀察者。而觀察者所做的只是被動(dòng)的接受主題提供的數(shù)據(jù)

觀察者模式解決的問題

講了這么多,其實(shí)觀察者模式最主要的作用就是讓主題和觀察者松耦合:即這兩個(gè)對(duì)象雖然互相可以交互,但是它們都不清楚彼此的細(xì)節(jié)

主題只知道觀察者實(shí)現(xiàn)了Observer接口,它并不需要知道觀察者的具體類是誰(shuí),也不需要了解它究竟實(shí)現(xiàn)了什么,它只需要調(diào)用觀察者的update將數(shù)據(jù)更新過去即可。

同樣的,因?yàn)橹黝}依賴的只是實(shí)現(xiàn)了Observer接口的對(duì)象列表,所以無論我們是對(duì)觀察者增加還是刪除,都不會(huì)對(duì)主題造成影響,主題也不需要為了兼容這些觀察者而去修改代碼。

甚至我們還可以在其他地方獨(dú)立的復(fù)用主題和觀察者,例如我們新增一個(gè)新的主題,又或者是新增一個(gè)觀察者,由于二者并非緊耦合,所以不會(huì)有任何的影響。

總結(jié)一下就是,這種設(shè)計(jì)將對(duì)象之間的互相依賴降到了最低,因此我們的程序具有彈性,能夠應(yīng)對(duì)各種變化。


觀察者模式大顯身手

回到上面的問題,當(dāng)我們的公眾號(hào)發(fā)布新內(nèi)容的時(shí)候,我們會(huì)將這些內(nèi)容推送到所有的入駐平臺(tái)中,這正好就符合上面所說的觀察者模式的場(chǎng)景。此時(shí)公眾號(hào)充當(dāng)主題對(duì)象,而平臺(tái)充當(dāng)觀察者。

此時(shí)完整的關(guān)系圖如下

根據(jù)上面所提到的內(nèi)容,我們抽象出具體的主題接口和觀察者接口。為了方便使用不同語(yǔ)言的讀者閱讀,我會(huì)盡量少用C++的特性,如果還是有不理解的可以私信或者評(píng)論區(qū)留言。

主題接口只需要提供必須的注冊(cè)、刪除、發(fā)布即可

class Subject { public:virtual ~Subject() = default;virtual void registerObserver(Observer*) = 0; //注冊(cè)觀察者virtual void removeObserver(Observer*) = 0; //移除觀察者virtual void notifyObservers() = 0; //通知所有觀察者 };

觀察者被動(dòng)等待主題的數(shù)據(jù),所以我們也只提供一個(gè)更新接口供主題更新數(shù)據(jù)

class Observer { public:virtual ~Observer() = default;virtual void update(const std::string& url, const std::string& title, const std::string& desc) = 0; //更新數(shù)據(jù) };

考慮到每個(gè)平臺(tái)獲取到新內(nèi)容都必定要將其展示出來,而每個(gè)平臺(tái)展示的方式又有所不同,所以我們將其再抽象為一個(gè)接口類,觀察者需要繼承這個(gè)類并實(shí)現(xiàn)自己的展示方法

class DisplayElement { public:virtual ~DisplayElement() = default;virtual void display() = 0; //顯示數(shù)據(jù) };

下面就開始具體實(shí)例的實(shí)現(xiàn)吧

為了保證不會(huì)對(duì)同一平臺(tái)重復(fù)發(fā)送,以及后續(xù)可能會(huì)對(duì)某些平臺(tái)單獨(dú)推送內(nèi)容,我們使用一個(gè)哈希表來存儲(chǔ)所有入駐的平臺(tái)

//主題派生子類 class ScienceFictionPusher : public Subject { public://增加觀察者void registerObserver(Observer* observer){_observers.insert(observer);}//刪除觀察者void removeObserver(Observer* observer){_observers.erase(observer);}//向所有平臺(tái)推送內(nèi)容void notifyObservers(){for(const auto& ob : _observers){ob->update(_url, _title, _desc);}}//推送新內(nèi)容void newPush(){notifyObservers();}//設(shè)置新內(nèi)容,當(dāng)有新內(nèi)容發(fā)布的時(shí)候,就會(huì)自動(dòng)推送給所有的平臺(tái)void setNewFiction(const std::string& url, const std::string& title, const std::string& desc){_url = url;_title = title;_desc = desc;newPush();}private:std::string _url; //小說鏈接std::string _title; //小說名std::string _desc; //小說簡(jiǎn)介std::unordered_set<Observer*> _observers; //入駐的平臺(tái) };

當(dāng)有新的平臺(tái)想要入駐的時(shí)候,它只需要繼承觀察者類并實(shí)現(xiàn)update接口即可,同時(shí)由于我們接收新內(nèi)容后還需要在自身平臺(tái)中顯示,所以還需要繼承發(fā)布內(nèi)容類,并實(shí)現(xiàn)display接口

為了方便注冊(cè)和刪除觀察者,我們需要保存一個(gè)指向主題的指針

//觀察者派生子類 class Zhihu : public Observer, public DisplayElement { public:Zhihu(Subject* ScienceFictionPusher): _ScienceFictionPusher(ScienceFictionPusher){_ScienceFictionPusher->registerObserver(this);}~Zhihu(){_ScienceFictionPusher->removeObserver(this);}//實(shí)現(xiàn)更新接口,讓主題主動(dòng)推送數(shù)據(jù)void update(const std::string& url, const std::string& title, const std::string& desc){_url = url;_title = title;_desc = desc;display();}//在平臺(tái)中顯示推送的內(nèi)容void display(){std::cout << "知乎每日書籍推薦:" << std::endl;std::cout << "鏈接:" << _url << std::endl;std::cout << "標(biāo)題:" << _title << std::endl;std::cout << "簡(jiǎn)介:" << _desc << "\n" <<std::endl; }private:std::string _url; //小說鏈接std::string _title; //小說名std::string _desc; //小說簡(jiǎn)介Subject* _ScienceFictionPusher; //主題對(duì)象,方便注冊(cè)和刪除 };

其他的觀察者也類似,為了節(jié)省篇幅這里就不多寫了,下面寫個(gè)簡(jiǎn)單的程序測(cè)試一下

int main() {ScienceFictionPusher* _subject = new ScienceFictionPusher;Douban* douban = new Douban(_subject);Zhihu* zhihu = new Zhihu(_subject);_subject->setNewFiction("www.aaaaaaa.com", "三體", "作品講述了地球人類文明和三體文明的信息交流、生死搏殺及兩個(gè)文明在宇宙中的興衰歷程。");_subject->setNewFiction("www.bbbbbbb.com", "球形閃電", "描述了一個(gè)歷經(jīng)球狀閃電的男主角對(duì)其歷盡艱辛的研究歷程,向我們展現(xiàn)了一個(gè)獨(dú)特、神秘而離奇的世界");delete zhihu;delete douban;delete _subject;return 0; }

我們添加了知乎和豆瓣兩個(gè)觀察者,并且連續(xù)推送了三體和球形閃電這兩條內(nèi)容

可以看到,測(cè)試結(jié)果沒有問題


總結(jié)

要點(diǎn)

  • 觀察者模式定義了對(duì)象之間一對(duì)多的關(guān)系
  • 觀察者模式使得我們可以獨(dú)立地改變主題與觀察者,從而使二者之間的依賴關(guān)系達(dá)致松耦合。
  • 主題發(fā)送通知時(shí),需要遍歷觀察者,因此其知道觀察者的存在
  • 觀察者自己決定是否需要訂閱通知,主題對(duì)象對(duì)此一無所知。

應(yīng)用場(chǎng)景

觀察者模式應(yīng)該可以說是應(yīng)用最多、影響最廣的模式之一,它通常應(yīng)用于游戲引擎、GUI、郵件訂閱等場(chǎng)景

場(chǎng)景1 :游戲中的事件監(jiān)控

例如我們?cè)O(shè)計(jì)了一個(gè)RPG游戲,當(dāng)我們的角色移動(dòng)到敵人的視野范圍時(shí),周圍的敵人就會(huì)向角色移動(dòng)并且發(fā)起攻擊。當(dāng)我們移動(dòng)到陷阱的觸發(fā)位置時(shí),陷阱就會(huì)對(duì)我們?cè)斐蓚Α.?dāng)我們移動(dòng)到泉水時(shí),泉水又會(huì)為角色提供治療或者BUFF。

在上面的例子中,我們的角色就是一個(gè)主題,而泉水、陷阱、敵人這些就是觀察者,當(dāng)我們做出了某種舉動(dòng)的時(shí)候,就會(huì)通知它們這些事件的發(fā)生,它們就會(huì)做出一個(gè)具體的響應(yīng)。這樣就能夠保證事件實(shí)時(shí)的同步,以及方便我們進(jìn)行拓展,后續(xù)向增加新事件例如減速的泥潭等內(nèi)容只需要將其注冊(cè)為觀察者并實(shí)現(xiàn)邏輯即可。

場(chǎng)景2:GUI界面的事件偵聽

在GUI界面中,通常有著許多的選項(xiàng), 而在這些選項(xiàng)背后,通常又有多個(gè)負(fù)責(zé)不同功能的偵聽者等待我們的結(jié)果,當(dāng)我們按下這個(gè)按鈕的時(shí)候,就會(huì)通知負(fù)責(zé)這一功能的一系列偵聽者響應(yīng)號(hào)召,執(zhí)行它們各自的工作,這也是一種觀察者模式


生產(chǎn)者-消費(fèi)者模型 VS 觀察者模式

說到數(shù)據(jù)的生產(chǎn)和發(fā)布、解耦合這兩方面,那就難免要提到生產(chǎn)者消費(fèi)者模型,下面給出它們兩個(gè)的對(duì)比圖。

如果不了解生產(chǎn)者消費(fèi)者模型的可以參考我的往期博客
操作系統(tǒng):生產(chǎn)者消費(fèi)者模型的兩種實(shí)現(xiàn)(C++)


相同點(diǎn)

  • 主要作用都是解耦合
  • 兩者都是行為模式,本質(zhì)上都是發(fā)布-消費(fèi)兩個(gè)行為

不同點(diǎn)

  • 觀察者模式是一對(duì)多,一條消息可以被多個(gè)觀察者使用
  • 生產(chǎn)者-消費(fèi)者模型是多對(duì)多的,并且一條消息只能被一個(gè)消費(fèi)者使用
  • 觀察者模式可以同步實(shí)現(xiàn),也可以異步實(shí)現(xiàn)
  • 生產(chǎn)者消費(fèi)者模式依賴于交易場(chǎng)所,只能異步實(shí)現(xiàn)
  • 觀察者模式中主題知道觀察者的存在,因?yàn)樗枰闅v訂閱列表發(fā)送通知,因此兩者之間還是存在微弱的耦合關(guān)系
  • 生產(chǎn)者和消費(fèi)者借助交易場(chǎng)所(中間隊(duì)列),它們只需要往隊(duì)列中生成/消費(fèi)數(shù)據(jù),因此不需要知道對(duì)方的存在,屬于完全解耦

完整代碼及文檔

如果有需要完整代碼或者markdown文檔的同學(xué)可以點(diǎn)擊下面的github鏈接
github

總結(jié)

以上是生活随笔為你收集整理的趣谈设计模式 | 观察者模式(Observer) :消息的发布与订阅的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。