设计模式学习笔记-观察者模式

1. 概述

有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

2. 解决的问题

将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。

3. 模式中的角色

3.1 抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

3.2 具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

3.3 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

3.4 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

4. 模式解读

4.1 观察者模式的类图

4.2 观察者模式的代码

//////抽象主题类///publicabstractclassSubject

{privateIList observers =newList();//////增加观察者//////publicvoidAttach(Observer observer)

{

observers.Add(observer);

}//////移除观察者//////publicvoidDetach(Observer observer)

{

observers.Remove(observer);

}//////向观察者(们)发出通知///publicvoidNotify()

{foreach(Observer oinobservers)

{

o.Update();

}

}

}//////抽象观察者类,为所有具体观察者定义一个接口,在得到通知时更新自己///publicabstractclassObserver

{publicabstractvoidUpdate();

}//////具体观察者或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。///publicclassConcreteSubject : Subject

{privatestringsubjectState;//////具体观察者的状态///publicstringSubjectState

{get{returnsubjectState; }set{ subjectState =value; }

}

}//////具体观察者,实现抽象观察者角色所要求的更新接口,已是本身状态与主题状态相协调///publicclassConcreteObserver : Observer

{privatestringobserverState;privatestringname;privateConcreteSubject subject;//////具体观察者用一个具体主题来实现///publicConcreteSubject Subject

{get{returnsubject; }set{ subject =value; }

}publicConcreteObserver(ConcreteSubject subject,stringname)

{this.subject =subject;this.name =name;

}//////实现抽象观察者中的更新操作///publicoverridevoidUpdate()

{

observerState=subject.SubjectState;

Console.WriteLine("The observer's state of {0} is {1}", name, observerState);

}

}

4.3 客户端代码

classProgram

{staticvoidMain(string[] args)

{//具体主题角色通常用具体自来来实现ConcreteSubject subject =newConcreteSubject();

subject.Attach(newConcreteObserver(subject,"Observer A"));

subject.Attach(newConcreteObserver(subject,"Observer B"));

subject.Attach(newConcreteObserver(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 代码实现

//////抽象观察者///publicinterfaceISubject

{voidNotify();

}//////工作岗位,作为这里的观察者的抽象///publicabstractclassJobStation

{publicabstractvoidUpdate();

}//////具体主题,这里是客户///publicclassCustomer : ISubject

{privatestringcustomerState;privateIList observers =newList();//////增加观察者//////publicvoidAttach(JobStation observer)

{this.observers.Add(observer);

}//////移除观察者//////publicvoidDetach(JobStation observer)

{this.observers.Remove(observer);

}//////客户状态///publicstringCustomerState

{get{returncustomerState; }set{ customerState =value; }

}publicvoidNotify()

{foreach(JobStation oinobservers)

{

o.Update();

}

}

}//////会计///publicclassAccountant : JobStation

{privatestringaccountantState;privateCustomer customer;publicAccountant(Customer customer)

{this.customer =customer;

}//////更新状态///publicoverridevoidUpdate()

{if(customer.CustomerState =="已付款")

{

Console.WriteLine("我是会计,我来开具发票。");

accountantState="已开发票";

}

}

}//////出纳///publicclassCashier : JobStation

{privatestringcashierState;privateCustomer customer;publicCashier(Customer customer)

{this.customer =customer;

}publicoverridevoidUpdate()

{if(customer.CustomerState =="已付款")

{

Console.WriteLine("我是出纳员,我给登记入账。");

cashierState="已入账";

}

}

}//////配送员///publicclassDilliveryman : JobStation

{privatestringdillivierymanState;privateCustomer customer;publicDilliveryman(Customer customer)

{this.customer =customer;

}publicoverridevoidUpdate()

{if(customer.CustomerState =="已付款")

{

Console.WriteLine("我是配送员,我来发货。");

dillivierymanState="已发货";

}

}

}

7.2.3 客户端代码

classProgram

{staticvoidMain(string[] args)

{

Customer subject=newCustomer();

subject.Attach(newAccountant(subject));

subject.Attach(newCashier(subject));

subject.Attach(newDilliveryman(subject));

subject.CustomerState="已付款";

subject.Notify();

Console.Read();

}

}

运行结果:

我是会计,我来开具发票。

我是出纳员,我给登记入账。

我是配送员,我来发货。

7.3 事件实现

7.3.1 类图

通过类图来看,观察者和主题之间已经不存在任何依赖关系了。

7.3.2 代码实现

//////抽象主题///publicinterfaceISubject

{voidNotify();

}//////声明委托///publicdelegatevoidCustomerEventHandler();//////具体主题///publicclassCustomer : ISubject

{privatestringcustomerState;//声明一个委托事件,类型为 CustomerEventHandlerpubliceventCustomerEventHandler Update;publicvoidNotify()

{if(Update !=null)

{//使用事件来通知给订阅者Update();

}

}publicstringCustomerState

{get{returncustomerState; }set{ customerState =value; }

}

}//////财务,已经不需要实现抽象的观察者类,并且不用引用具体的主题///publicclassAccountant

{privatestringaccountantState;publicAccountant()

{ }//////开发票///publicvoidGiveInvoice()

{

Console.WriteLine("我是会计,我来开具发票。");

accountantState="已开发票";

}

}//////出纳,已经不需要实现抽象的观察者类,并且不用引用具体的主题///publicclassCashier

{privatestringcashierState;publicvoidRecoded()

{

Console.WriteLine("我是出纳员,我给登记入账。");

cashierState="已入账";

}

}//////配送员,已经不需要实现抽象的观察者类,并且不用引用具体的主题///publicclassDilliveryman

{privatestringdillivierymanState;publicvoidDilliver()

{

Console.WriteLine("我是配送员,我来发货。");

dillivierymanState="已发货";

}

}

7.3.3 客户端代码

classProgram

{staticvoidMain(string[] args)

{

Customer subject=newCustomer();

Accountant accountant=newAccountant();

Cashier cashier=newCashier();

Dilliveryman dilliveryman=newDilliveryman();//注册事件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();

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容