設計模式學習筆記-觀察者模式

1. 概述

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

2. 解決的問題

  將一個系統分割成一個一些類相互協作的類有一個不好的副作用,那就是需要維護相關對象間的一致性。我們不希望為了維持一致性而使各類緊密耦合,這樣會給維護、擴展和重用都帶來不便。觀察者就是解決這類的耦合關系的。

3. 模式中的角色

  3.1 抽象主題(Subject):它把所有觀察者對象的引用保存到一個聚集里,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象。

  3.2 具體主題(ConcreteSubject):將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。

  3.3 抽象觀察者(Observer):為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。

  3.4 具體觀察者(ConcreteObserver):實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題狀態協調。

4. 模式解讀

  4.1?觀察者模式的類圖  

  

  4.2?觀察者模式的代碼

復制代碼
    /// <summary>/// 抽象主題類/// </summary>public abstract class Subject{private IList<Observer> observers = new List<Observer>();/// <summary>/// 增加觀察者/// </summary>/// <param name="observer"></param>public void Attach(Observer observer){observers.Add(observer);}/// <summary>/// 移除觀察者/// </summary>/// <param name="observer"></param>public void Detach(Observer observer){observers.Remove(observer);}/// <summary>/// 向觀察者(們)發出通知/// </summary>public void Notify(){foreach (Observer o in observers){o.Update();}}}/// <summary>/// 抽象觀察者類,為所有具體觀察者定義一個接口,在得到通知時更新自己/// </summary>public abstract class Observer{public abstract void Update();}/// <summary>/// 具體觀察者或具體通知者,將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色通常用一個具體子類實現。/// </summary>public class ConcreteSubject : Subject{private string subjectState;/// <summary>/// 具體觀察者的狀態/// </summary>public string SubjectState{get { return subjectState; }set { subjectState = value; }}}/// <summary>/// 具體觀察者,實現抽象觀察者角色所要求的更新接口,已是本身狀態與主題狀態相協調/// </summary>public class ConcreteObserver : Observer{private string observerState;private string name;private ConcreteSubject subject;/// <summary>/// 具體觀察者用一個具體主題來實現/// </summary>public ConcreteSubject Subject{get { return subject; }set { subject = value; }}public ConcreteObserver(ConcreteSubject subject, string name){this.subject = subject;this.name = name;}/// <summary>/// 實現抽象觀察者中的更新操作/// </summary>public override void Update(){observerState = subject.SubjectState;Console.WriteLine("The observer's state of {0} is {1}", name, observerState);}}
復制代碼

  4.3?客戶端代碼

復制代碼
    class Program{static void Main(string[] args){// 具體主題角色通常用具體自來來實現ConcreteSubject subject = new ConcreteSubject();subject.Attach(new ConcreteObserver(subject, "Observer A"));subject.Attach(new ConcreteObserver(subject, "Observer B"));subject.Attach(new ConcreteObserver(subject, "Observer C"));subject.SubjectState = "Ready";subject.Notify();Console.Read();}}
復制代碼

  運行結果

  

5. 模式總結

  5.1 優點

    5.1.1 觀察者模式解除了主題和具體觀察者的耦合,讓耦合的雙方都依賴于抽象,而不是依賴具體。從而使得各自的變化都不會影響另一邊的變化。

  5.2 缺點

    5.2.1 依賴關系并未完全解除,抽象通知者依舊依賴抽象的觀察者。

  5.3 適用場景

    5.3.1 當一個對象的改變需要給變其它對象時,而且它不知道具體有多少個對象有待改變時。

    5.3.2 一個抽象某型有兩個方面,當其中一個方面依賴于另一個方面,這時用觀察者模式可以將這兩者封裝在獨立的對象中使它們各自獨立地改變和復用。

?

6. 模式引申,應用C#中的事件委托來徹底解除通知者和觀察者之間的耦合。

   6.1 關于委托的定義:委托是一種引用方法的類型。一旦為委托分配了方法,委托將與該方法有相同的行為。委托方法可以像其它任何方法一樣,具有參數和返回值。委托可以看作是對函數(方法)的的抽象,是函數的“類”,委托的實例代表一個(或多個)具體的函數,它可以是多播的。

   6.2 關于事件:事件基于委托,為委托提供了一種發布/訂閱機制。事件的訂閱與取消與我們剛才講的觀察者模式中的訂閱與取消類似,只是表現形式有所不同。在觀察者模式中,訂閱使用方法Attach()來進行;在事件的訂閱中使用“+=”。類似地,取消訂閱在觀察者模式中用Dettach(),而事件的取消用“-=”。

?

7. 下面例子分別用觀察者模式,事件機制來實現

  7.1 實例描述:客戶支付了訂單款項,這時財務需要開具發票,出納需要記賬,配送員需要配貨。

  7.2 觀察者模式的實現

    7.2.1 類圖

    

    7.2.2 代碼實現

復制代碼
    /// <summary>/// 抽象觀察者/// </summary>public interface ISubject{void Notify();}/// <summary>/// 工作崗位,作為這里的觀察者的抽象/// </summary>public abstract class JobStation{public abstract void Update();}/// <summary>/// 具體主題,這里是客戶/// </summary>public class Customer : ISubject{private string customerState;private List<JobStation> observers = new List<JobStation>();/// <summary>/// 增加觀察者/// </summary>/// <param name="observer"></param>public void Attach(JobStation observer){this.observers.Add(observer);}/// <summary>/// 移除觀察者/// </summary>/// <param name="observer"></param>public void Detach(JobStation observer){this.observers.Remove(observer);}/// <summary>/// 客戶狀態/// </summary>public string CustomerState{get { return customerState; }set { customerState = value; }}public void Notify(){foreach (JobStation o in observers){o.Update();}}}/// <summary>/// 會計/// </summary>public class Accountant : JobStation{private string accountantState;private Customer customer;public Accountant(Customer customer){this.customer = customer;}/// <summary>/// 更新狀態/// </summary>public override void Update(){if (customer.CustomerState == "已付款"){Console.WriteLine("我是會計,我來開具發票。");accountantState = "已開發票";}}}/// <summary>/// 出納/// </summary>public class Cashier : JobStation{private string cashierState;private Customer customer;public Cashier(Customer customer){this.customer = customer;}public override void Update(){if (customer.CustomerState == "已付款"){Console.WriteLine("我是出納員,我給登記入賬。");cashierState = "已入賬";}}}/// <summary>/// 配送員/// </summary>public class Dilliveryman : JobStation{private string dillivierymanState;private Customer customer;public Dilliveryman(Customer customer){this.customer = customer;}public override void Update(){if (customer.CustomerState == "已付款"){Console.WriteLine("我是配送員,我來發貨。");dillivierymanState = "已發貨";}}}
復制代碼

    7.2.3 客戶端代碼

復制代碼
    class Program{static void Main(string[] args){Customer subject = new Customer();subject.Attach(new Accountant(subject));subject.Attach(new Cashier(subject));subject.Attach(new Dilliveryman(subject));subject.CustomerState = "已付款";subject.Notify();Console.Read();}}
復制代碼

    運行結果:

    我是會計,我來開具發票。
    我是出納員,我給登記入賬。
    我是配送員,我來發貨。

?

  7.3 事件實現

    7.3.1 類圖

    

    通過類圖來看,觀察者和主題之間已經不存在任何依賴關系了。

    7.3.2 代碼實現

    

復制代碼
    /// <summary>/// 抽象主題/// </summary>public interface ISubject{void Notify();}/// <summary>/// 聲明委托/// </summary>public delegate void CustomerEventHandler();/// <summary>/// 具體主題/// </summary>public class Customer : ISubject{private string customerState;// 聲明一個委托事件,類型為 CustomerEventHandlerpublic event CustomerEventHandler Update;public void Notify(){if (Update != null){// 使用事件來通知給訂閱者Update();}}public string CustomerState{get { return customerState; }set { customerState = value; }}}/// <summary>/// 財務,已經不需要實現抽象的觀察者類,并且不用引用具體的主題/// </summary>public class Accountant{private string accountantState;public Accountant(){ }/// <summary>/// 開發票/// </summary>public void GiveInvoice(){Console.WriteLine("我是會計,我來開具發票。");accountantState = "已開發票";}}/// <summary>/// 出納,已經不需要實現抽象的觀察者類,并且不用引用具體的主題/// </summary>public class Cashier{private string cashierState;public void Recoded(){Console.WriteLine("我是出納員,我給登記入賬。");cashierState = "已入賬";}}/// <summary>/// 配送員,已經不需要實現抽象的觀察者類,并且不用引用具體的主題/// </summary>public class Dilliveryman{private string dillivierymanState;public void Dilliver(){Console.WriteLine("我是配送員,我來發貨。");dillivierymanState = "已發貨";}}
復制代碼

    7.3.3 客戶端代碼

復制代碼
    class Program{static void Main(string[] args){Customer subject = new Customer();Accountant accountant = new Accountant();Cashier cashier = new Cashier();Dilliveryman dilliveryman = new Dilliveryman();// 注冊事件subject.Update += accountant.GiveInvoice;subject.Update += cashier.Recoded;subject.Update += dilliveryman.Dilliver;/** 以上寫法也可以用下面代碼來替換subject.Update += new CustomerEventHandler(accountant.GiveInvoice);subject.Update += new CustomerEventHandler(cashier.Recoded);subject.Update += new CustomerEventHandler(dilliveryman.Dilliver);*/subject.CustomerState = "已付款";subject.Notify();Console.Read();}}
復制代碼

    運行結果

    我是會計,我來開具發票。
    我是出納員,我給登記入賬。
    我是配送員,我來發貨。

轉載于:https://www.cnblogs.com/Free-Thinker/p/4730227.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/273053.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/273053.shtml
英文地址,請注明出處:http://en.pswp.cn/news/273053.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Handler與多線程

1、Handler介紹 在Android開發中&#xff0c;我們常會使用單獨的線程來完成某些操作&#xff0c;比如用一個線程來完成從網絡上下的圖片&#xff0c;然后顯示在一個ImageView上&#xff0c;在多線程操作時&#xff0c;Android中必須保證以下兩點&#xff1a; &#xff08;1&…

oracle read only 事務,oracle set transaction read only與dbms_transaction實現事務transaction控制...

SQL> show userUser is "SYS"SQL> set transaction read only;Transaction setSQL> insert into t_table values(3);1 row insertedSQL> commit;Commit complete---sys用戶 set transaction read only不生效SQL> select * from t_table;A------------…

oracle report builder 6i下載,oracle report builder 6i - 數據模型中的SQL查詢代碼

我是Vijetha&#xff0c;我正在研究報告6i&#xff0c;我很陌生 . 我有以下查詢 .在front_end中&#xff0c;在Reports Parameter中&#xff0c;當用戶單擊“運行”按鈕時&#xff0c;它將詢問START_DATE和END_DATE輸入 .如果用戶提供START_DATE和END_DATE或者不提供輸入&#…

HTML/CSS/JavaScript學習總結(轉)

HTML 網站開發的主要原則是&#xff1a; – 用標簽元素HTML描述網頁的內容結構&#xff1b; – 用CSS描述網頁的排版布局&#xff1b; – 用JavaScript描述網頁的事件處理&#xff0c;即鼠標或鍵盤在網頁元素上的動作后的程序 HTML&#xff08;Hyper Text Mark-up Language 超文…

oracle引用vs,VS2013中使用oracle,有關引用哪個.dll

Oracle、Microsoft 和第三方供應商都提供了針對 Oracle 產品進行了優化的數據供應程序。 Oracle 和 Microsoft 免費提供其 Oracle 數據供應程序。訪問 Oracle 的操作有些類似于對 Sql Server 的操作。對Oracle 的訪問有以下幾種數據提供程序。 Microsoft.NET Oracle 提供程序 O…

貪心方法

1.背包問題 按效益值/重量 進行排序輸入 2.帶限期的作用排序 按效益值進行排序輸入 3 最小生成樹&#xff1a; 貪心方法&#xff1a;每次計入成本最小的邊 原樹T&#xff0c; 欲構造的最小生成樹T Prim&#xff1a; 從T中選與T中結點相連的成本最小的邊。 且&#xff1a;邊之前…

oracle語法官方文檔,Oracle官方文檔必備語法知識

很多Oracle DBA雖然接觸Oracle時間很長&#xff0c;但是一旦想不起語法或找不出相應參數時&#xff0c;習慣百度或谷歌。雖然已經下載了官方文檔&#xff0c;但是Oracle官方文檔必備語法知識[日期&#xff1a;2015-04-21]來源&#xff1a;Linux社區作者&#xff1a;kuqlan[字體…

新中大oracle實列名,新中大財務軟件操作流程(完整版)

新中大財務軟件最基本的三個模塊&#xff1a;核算單位、財務處理系統、報表處理系統。簡單地說&#xff0c;核算單位模塊是用于建賬&#xff0c;財務處理系統用于登賬&#xff0c;報表處理系統用于出報表的。一、總賬處理系統1、建賬套雙擊財務軟件圖標 → 在登錄界面選擇用戶編…

編寫DLL所學所思(1)——導出函數

燭秋 http://www.cnblogs.com/cswuyg/archive/2011/09/30/dll.html 動態鏈接庫的使用有兩種方式&#xff0c;一種是顯式調用。一種是隱式調用。 &#xff08;1&#xff09; 顯式調用&#xff1a;使用LoadLibrary載入動態鏈接庫、使用GetProcAddress獲取某函數地址。 &am…

linux切換任務命令,Linux top詳解之交互命令、命令行選項

top交互命令我們之前說過top是一個交互命令。上一節我們已經遇到了一些命令。這里我們會探索更多的命令。2.1 ‘h’: 幫助首先&#xff0c;我們可以用’h’或者’?’顯示交互命令的幫助菜單。2.2 “或者”: 刷新顯示top命令默認在一個特定間隔(3秒)后刷新顯示。要手動刷新&am…

linux 內核地址隨機化,GNU/Linux內核的地址隨機化

地址空間布局隨機化(ASLR)是一項增加安全性的技術&#xff0c;***者發現漏洞之后開始編寫exploit時如果要考慮繞過ASLR這會增加編寫exploit的難度&#xff0c;最早是2001年Grsecurity社區(強悍的社區&#xff0c;直到今天還在為各種各樣的加固為自由軟件安全社區作出持續而杰出…

Yii2的一些問題

Yii2中刪除能不能串著用 Yii2中find、findAll有什么區別 Yii2中User::findOne($id)和User::find->where([id>1])->one; 會員登錄信息 是以什么樣的形式存放在Yii::$app->user->identity 里面的&#xff1f; session的形式 http://www.cnblogs.com/kuyuecs/archi…

linux系統硬盤設置密碼,LUKS:Linux下磁盤加密

Linux下磁盤加密LUKS(Linux Unified Key Setup)為Linux硬盤加密提供了一種標準&#xff0c;它不僅能通用于不同的Linux發行版本&#xff0c;還支持多用戶/口令。因為它的加密密鑰獨立于口令&#xff0c;所以如果口令失密&#xff0c;我們可以迅速改變口令而無需重新加密真個硬盤…

Hibernate查詢

9.1 Hibernate數據查詢 數據查詢與檢索是Hibernate的一個亮點。Hibernate的數據查詢方式主要有3種&#xff0c;它們是&#xff1a; l Hibernate Query Language&#xff08;HQL&#xff09; l Criteria Query l Native SQL 下面對這3種查詢方式分別進…

linux x86 io端口映射,linux中的 IO端口映射和IO內存映射

下面是今天看到兩篇關于linux中的 IO端口映射和IO內存映射的文章,時間關系,沒來得及深入理解,有空好好看看CPU地址空間CPU地址空間(一)地址的概念1)物理地址&#xff1a;CPU地址總線傳來的地址&#xff0c;由硬件電路控制其具體含義。物理地址中很大一部分是留給內存條中的內存…

單例模式 創建對象

兩種選擇 1 使用pthread_once&#xff0c; once是類的成員變量 只執行一次Create create的作用是創建一個對象 2 使用 static lock 如下所示&#xff0c;注意lock必須是static的&#xff0c;否則是局部變量&#xff0c;每個線程都有自己的lock&#xff0c;無法保證只執行一次。…

Linux c編譯庫路徑,【一點一點學Linux C】交叉編譯時候如何配置連接庫的搜索路徑...

交叉編譯的時候不能使用本地(i686機器&#xff0c;即PC機器&#xff0c;研發機器)機器上的庫&#xff0c;但是在做編譯鏈接的時候默認的是使用本地庫&#xff0c;即/usr/lib,/lib兩個目錄。因此&#xff0c;在交叉編譯的時候&#xff0c;要采取一些方法使得在編譯鏈接的時候找到…

[NBUT 1458 Teemo]區間第k大問題,劃分樹

裸的區間第k大問題&#xff0c;劃分樹搞起。 #pragma comment(linker, "/STACK:10240000") #include <map> #include <set> #include <cmath> #include <ctime> #include <deque> #include <queue> #include <stack> #inc…

Linux的軟件包封裝格式有,linux軟件安裝包詳解---全

詳細介紹了常見的四種Linux應用軟件安裝包及其安裝方法。一、解析Linux應用軟件安裝包&#xff0c;通常Linux應用軟件的安裝包有四種&#xff1a;1) tar包&#xff0c;如software-1.2.3-1.tar.gz。他是使用UNIX系統的打包工具tar打包的。2) rpm包&#xff0c;如software-1.2.3-…

人生的第一個博客(●'?'●)??--開博典禮

嘛&#xff0c;說實話&#xff0c;現在才開始&#xff0c;實在是有點晚了&#xff0c;一不小心大學都過去1年了_(:3 」∠)_ 我在專業方面的起步也是相當晚的&#xff0c;身為計算機專業&#xff0c;編程卻從大學才開始正式接觸&#xff0c;進入大學時其他方面的能力也都約等于0…