爲了便於理解,加深記憶。得舉一個最難讓人忘記的應用場景,因爲之前我也是學了忘,忘了學,當你想不起來觀察者模式的時候,通過回憶這個例子,就能很快想起觀察者模式的應用
- 環境
當我們去上課的時候,需要記錄老師的電話號碼,那麼當這位老師變更了他的電話號碼,那麼需要再上課的時候,老師主動告知同學們,同學們重新記錄一遍,老師再變更電話號碼,同學們再重新記錄一遍,重複以上流程。
- 應用背景
當某一個對象發生變化的時候,就需要把這種變化通知給其他對象,讓其他對象針對這種變化,作出相應調整,在這種場景下就可以使用觀察者模式
- 第一種設計方案
下面的代碼--缺點:學生對象裏存儲個老師的電話號碼屬性TPhone,老師變更電話號碼後,得顯式的告知給每一個學生,並沒有主動通知
1、Teacher類
namespace Observer { public class Teacher { private string _phone; public string Phone { get { return _phone; } set { _phone = value; } } public Teacher(string phone) { this.Phone = phone; } } }
2、Student類
namespace Observer { public class Student { private string _name; private string _tPhone; public string Name { get { return _name; } set { _name = value; } } public string TPhone { get { return _tPhone; } set { _tPhone = value; } } public Student(string name) { this.Name = name; } public void Show() { Console.WriteLine("Name:" + this.Name + "\nTeacher Phone:" + this.TPhone); } } }
3、調用
namespace Observer { class Program { static void Main(string[] args) { Teacher zhangTeacher = new Teacher("123456789"); Student zhangsan = new Student("張三"); Student lisi = new Student("李四"); Student wangwu = new Student("王五"); zhangsan.TPhone = zhangTeacher.Phone; lisi.TPhone = zhangTeacher.Phone; wangwu.TPhone = zhangTeacher.Phone; zhangsan.Show(); lisi.Show(); wangwu.Show(); //老師的電話號碼變更】 zhangTeacher.Phone = "987654321"; zhangsan.TPhone = zhangTeacher.Phone; lisi.TPhone = zhangTeacher.Phone; wangwu.TPhone = zhangTeacher.Phone; zhangsan.Show(); lisi.Show(); wangwu.Show(); Console.ReadKey(); } } }
- 第二種設計方案
下面的代碼--缺點:在學生的類裏面直接存了一個教師的對象,當一個學生裏面存了一個教師的對象之後,他們兩個就已經耦合在了一起,無論這個老師教我還是不教我,我是畢業了還是沒有畢業,老師的對象永遠在我裏面存儲着,就算我畢業了我不願意跟這個老師聯繫了,我們也沒有辦法刪除他,所以說他的擴展性和靈活性仍然是不好的,因爲在老師和學生底層直接發生了關聯關係
1、Teacher類
namespace Observer { public class Teacher { private string _phone; public string Phone { get { return _phone; } set { _phone = value; } } public Teacher(string phone) { this.Phone = phone; } } }
2、Student類
namespace Observer { public class Student { private string _name; private Teacher _teacher; public string Name { get { return _name; } set { _name = value; } } public Teacher Teacher { get { return _teacher; } set { _teacher = value; } } public Student(string name, Teacher teacher) { this.Name = name; this.Teacher = teacher; } public void Show() { Console.WriteLine("Name:" + this.Name + "\nTeacher Phone:" + Teacher.Phone); } } }
3、調用
namespace Observer { class Program { static void Main(string[] args) { Teacher zhangTeacher = new Teacher("張老師"); Student zhangsan = new Student("張三", zhangTeacher); Student lisi = new Student("李四", zhangTeacher); Student wangwu = new Student("王五", zhangTeacher); zhangsan.Show(); lisi.Show(); wangwu.Show(); //老師的電話號碼變更 Console.WriteLine("============================"); zhangTeacher.Phone = "987654321"; zhangsan.Show(); lisi.Show(); wangwu.Show(); Console.ReadKey(); } } }
- 觀察者模式詳解圖
觀察者模式中有2個角色,觀察者和被觀察者
- 觀察者模式實現步驟一
容器--Container
會有一個被觀察者等着別人看,看他的一系列變化,還會有一些觀察者看着他,註冊指的是如果你想要接收到被觀察者也就是主題的變化通知的話,你必須得執行註冊的操作,在主題對象當中會有1個集合存在,你想要成爲他的觀察者,就需要把自己註冊到這個集合當中,成爲觀察者列表中的一員
- 觀察者模式實現步驟二
通知--Notify
在主題對象被觀察者自身上面,它可能會發生一些狀態變化,一但自身狀態發生變化,它就會自動的去通知觀察者,怎麼樣去通知?之前已經在被觀察者自身存在了1個集合呀,集合裏存好了一個一個觀察者對象,那它就可以去循環這個集合,把這些觀察者一個個拿出來,告訴他們你要針對我的變化發生的響應
- 觀察者模式實現步驟三
撤銷註冊--Remove
我們可能一個對象成爲了主題對象的觀察者以後,觀察了一陣,發現我在某種情況下不願意觀察你了,我想不在接收你所有的變化信息,這個時候怎麼辦呢?這個時候撤銷註冊,把自己從被觀察者裏的觀察者列表集合裏刪除出去就好了,所以實現觀察者模式最重要的一點就是這個集合的存在
- 觀察者模式設計類圖
老師內部會存在一個學生集合,存在這個學生集合以後,它可以往這個集合裏面添加觀察者對象(具體的學生),所以要有註冊觀察者方法RegisterObserver(),當觀察者不願意觀察以後,它還可以把觀察者從隊列裏移除,所以還要有移除觀察者方法RemoveObserver(),最重要當我的狀態發生變化以後,我能夠通知到我的觀察者執行某個方法,所以會有通知觀察者方法NotifyObserver(),實際上我們是希望解耦的,我們不希望老師和學生直接發生調用關係,我們希望老師和學生解耦如果不解耦和第一種設計方案以及第二種設計方案是一樣的,如何讓他們解耦呢?那麼就回到了好設計原則中的依賴倒轉原則面向抽象進行編程,所以說在現有的老師和學生類之上,我們向上抽取,抽取出來兩個接口,一個是觀察者接口,觀察者接口的作用就是規定如果你想成爲觀察者,那麼你必須要有一個方法Update(),這個方法的目的就是實現讓某個通知過來以後你做相應的更新。一個是被觀察者接口,作爲學生我可以觀察老師,還可以觀察學院,同樣向上抽取,抽取一個被觀察者接口,如果你想成爲被觀察者,你就必須要具有這三種行爲
1、要能夠往被觀察者的觀察者列表裏註冊一個觀察者
2、要能夠從被觀察者的觀察者列表裏能夠把某一個觀察者移除
3、還要能夠自動的發送變化通知,通知給其他一系列的觀察者,讓他們完成自動更新的操作
- 代碼具體實現
1、觀察者類
namespace ObserverPattern { /// <summary> /// 觀察者抽象 /// </summary> public interface Obsorver { void Update(Object obj); } }
2、學生類繼承觀察者類
namespace ObserverPattern { public class Student:Obsorver { private string _name; private string _tPhone; public string Name { get { return _name; } set { _name = value; } } public string TPhone { get { return _tPhone; } set { _tPhone = value; } } public Student(string name) { this.Name = name; } public void Update(object obj) { this.TPhone = (string)obj; } public void Show() { Console.WriteLine("Name:" + this.Name + "\nTeacher Phone:" + this.TPhone); } } }
3、主題接口,裏面有註冊觀察者,移除觀察者,通知觀察者三個方法
namespace ObserverPattern { public interface Subject { /// <summary> /// 註冊觀察者 /// </summary> /// <param name="obj"></param> void RegisterObserver(Obsorver obj); /// <summary> /// 取消註冊的觀察者 /// </summary> /// <param name="obj"></param> void UnRegisterObserver(Obsorver obj); /// <summary> /// 通知觀察者 /// </summary> void NotifyObserver(); } }
4、教師類繼承自主題接口,實現註冊觀察者,移除觀察者,通知觀察者三個方法
namespace ObserverPattern { public class Teacher:Subject { private ArrayList stuList;//觀察者集合列表 private string _phone; public Teacher() { stuList = new ArrayList(); } public string Phone { get { return _phone; } set { _phone = value; NotifyObserver(); } } /// <summary> /// 註冊觀察者 /// </summary> /// <param name="obj"></param> public void RegisterObserver(Obsorver obj) { stuList.Add(obj); } /// <summary> /// 取消註冊的觀察者 /// </summary> /// <param name="obj"></param> public void UnRegisterObserver(Obsorver obj) { stuList.Remove(obj); } /// <summary> /// 通知觀察者 /// </summary> public void NotifyObserver() { for (int i = 0; i < stuList.Count; i++) { ((Obsorver)stuList[i]).Update(Phone); } } } }
5、調用
namespace ObserverPattern { class Program { static void Main(string[] args) { Teacher zhangTeacher = new Teacher(); Student zhangsan = new Student("張三"); Student lisi = new Student("李四"); Student wangwu = new Student("王五"); zhangTeacher.RegisterObserver(zhangsan); zhangTeacher.RegisterObserver(lisi); zhangTeacher.RegisterObserver(wangwu); //老師的電話號碼變更 zhangTeacher.Phone = "123456789"; //打印學生信息 zhangsan.Show(); lisi.Show(); wangwu.Show(); //從觀察者集合中移除張三同學 zhangTeacher.UnRegisterObserver(zhangsan); Console.WriteLine("============================"); //老師的電話號碼再變更 zhangTeacher.Phone = "987654321"; //再打印學生信息 zhangsan.Show(); lisi.Show(); wangwu.Show(); Console.ReadKey(); } } }
- 觀察者模式的應用場景
像訂閱雜誌,訂閱天氣預報,還有微信公衆號中的訂閱號
- .NET框架中觀察者模式的應用
在.NET框架中事件就是觀察者模式,還有Sql Server如果需要讀寫分離,讀數據庫訂閱寫數據庫,那麼也是觀察者模式
C#中事件是如何來實現的?我們是不是可以通過+=註冊事件,-=移除事件,其實事件的本身這種機制就是觀察者和被觀察者的關係,+=其實就是把自己註冊到觀察者列表裏,-=就是把自己移除出觀察者列表