16.观察者模式(Observer Pattern)
动机(Motivate):
在软件构建 过程中,我们需要为某些对象建立一种“通知依赖关系” --------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面 向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
意图(Intent):
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
-------《设计模式》GOF
结构图(Struct):

适用性:
1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
生活中的例子:
观 察者定义了对象间一对多的关系,当一个对象的状态变化时,所有依赖它的对象都得到通知并且自动地更新。在ATM取款,当取款成功后,以手机、邮件等方式进行通知。
代码实现:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
class BankAccount
{
private Emailer emailer; //强信赖关系 private Mobile phoneNumber; //强信赖关系 private double _money; public Emailer Emailer
{
get { return emailer; }
set { emailer = value; }
} public Mobile PhoneNumber
{
get { return phoneNumber; }
set { phoneNumber = value; }
} public double Money
{
get { return _money; }
set { _money = value; }
} public void WithDraw()
{
emailer.SendEmail(this);
phoneNumber.SenderNotification(this); }
}
class Emailer
{
private string _emailer; public Emailer(string emailer)
{
this._emailer = emailer;
} public void SendEmail(BankAccount ba)
{
//..
Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emailer, ba.Money);
}
}
class Mobile
{
private long _phoneNumber; public Mobile(long phoneNumber)
{
this._phoneNumber = phoneNumber;
} public void SenderNotification(BankAccount ba)
{
//..
Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _phoneNumber, ba.Money);
}
}
客户端调用如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
static void Main(string[] args)
{ ////------------------------------ 模式1 ---------------------------------------- //model1.BankAccount ba = new 模式1.BankAccount(); //model1.Emailer emailer = new model1.Emailer("abcdwxc@163.com");
//model1.Mobile mobile = new model1.Mobile(13901234567); //ba.Emailer = emailer;
//ba.PhoneNumber = mobile; //ba.Money = 2000; //ba.WithDraw(); ////-------------------------------------------------------------------------------
}
运行结果如下:
由此可见程序可以正常运行,但请注意BandAccount和Emailer及Mobile之间形成了一种双向的依赖关系,即BankAccount调用了Emailer及Mobile的方法,而Emailer及Mobile调用了BnadAccount类的属性。如果有其中一个类变化,有可能会引起另一个的变化。如果又需添加一种新的通知方式,就得在BankAccount的WithDraw()方法中增加对该中通知方式的调用。
显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的BankAccount类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消BankAccount和具体的通知对象之间依赖。
由此我们由左图转换到右图。
实例代码如下:
interface IObserverAccount
{
void Update(BankAccount ba);
}
class BankAccount
{
private List<IObserverAccount> obServers = new List<IObserverAccount>(); private double _money; public double Money
{
get { return _money; }
set { _money = value; }
} public void WithDraw()
{
foreach (IObserverAccount ob in obServers)
{
ob.Update(this);
}
} public void AddObServer(IObserverAccount ob)
{
this.obServers.Add(ob);
} public void RemoveObServer(IObserverAccount ob)
{
this.obServers.Remove(ob);
}
}
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
class Emailer : Pattern_P16_行为型模式_观察者模式.模式2.IObserverAccount
{
private string _emailer; public Emailer(string emailer)
{
this._emailer = emailer;
} public void Update(BankAccount ba)
{
Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emailer, ba.Money);
}
}
class Mobile : IObserverAccount
{
private long _phoneNumber; public Mobile(long phoneNumber)
{
this._phoneNumber = phoneNumber;
} public void Update(BankAccount ba)
{
Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _phoneNumber, ba.Money);
}
}
客户端与上方相同,其运行结果也相同。但BankAccount增加和删除通知对象时,还需对其进行修改。对此我们再做如下重构,在BankAccount中维护一个IObserver列表,同时提供相应的维护方法。
class BankAccount
{
private List<IObserverAccount> obServers = new List<IObserverAccount>(); private double _money; public double Money
{
get { return _money; }
set { _money = value; }
} public void WithDraw()
{
foreach (IObserverAccount ob in obServers)
{
ob.Update(this);
}
} public void AddObServer(IObserverAccount ob)
{
this.obServers.Add(ob);
} public void RemoveObServer(IObserverAccount ob)
{
this.obServers.Remove(ob);
}
}
此时客户端代码如下:
////------------------------------ 模式2 ----------------------------------------
//model2.BankAccount ba = new model2.BankAccount();
//ba.Money = 2000;
//model2.IObserverAccount emailer = new model2.Emailer("abcdwxc@163.com");
//model2.IObserverAccount phone = new model2.Mobile(13901234567);
//ba.AddObServer(emailer);
//ba.AddObServer(phone);
//ba.WithDraw();
////-------------------------------------------------------------------------------
走到这一步,已经有了Observer模式的影子了,BankAccount类不再依赖于具体的Emailer或Mobile,而是依赖于抽象的IObserverAccount。存在着的一个问题是Emailer或Mobile仍然依赖于具体的BankAccount,解决这样的问题很简单,只需要再对BankAccount类做一次抽象。如下图:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
internal abstract class Subject
{
private List<IObserverAccount> lObservers = new List<IObserverAccount>(); private double _money; public double Money
{
get { return _money; }
set { _money = value; }
} public Subject(double money)
{
this.Money = money;
} public void WithDraw()
{
foreach (IObserverAccount ob in lObservers)
{
ob.Update(this);
}
} public void AddObserverAccount(IObserverAccount ob)
{
lObservers.Add(ob);
} public void RemoveObserverAccount(IObserverAccount ob)
{
lObservers.Remove(ob);
}
}
internal interface IObserverAccount
{
void Update(Subject subject);
}
internal class BankAccount:Subject
{
public BankAccount(double money)
: base(money)
{ } }
internal class Emailer : Pattern_P16_行为型模式_观察者模式.模式3.IObserverAccount
{
private string _emailer; public Emailer(string emailer)
{
this._emailer = emailer;
} public void Update(Subject subject)
{
Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _emailer, subject.Money);
}
}
internal class Mobile : IObserverAccount
{
private long _phoneNumber; public Mobile(long phoneNumber)
{
this._phoneNumber = phoneNumber;
} public void Update(Subject subject)
{
Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _phoneNumber, subject.Money);
}
}
此时客户端实现如下:
////------------------------------ 模式3 ----------------------------------------
//model3.Subject subject = new model3.BankAccount(2000);
//model3.IObserverAccount emailer = new model3.Emailer("abcdwxc@163.com");
//model3.IObserverAccount mobile = new model3.Mobile(13901234567);
//subject.AddObserverAccount(emailer);
//subject.AddObserverAccount(mobile);
//subject.WithDraw();
////-------------------------------------------------------------------------------
推模式与拉模式
对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的 观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的 消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。
拉模式实现如下:
internal abstract class Subject
{
private List<IObserverAccount> lObservers = new List<IObserverAccount>(); private double _money; public double Money
{
get { return _money; }
set { _money = value; }
} public Subject(double money)
{
this._money = money;
} public void WithDraw()
{
foreach (IObserverAccount ob in lObservers)
{
ob.Update();
}
} public void AddObserver(IObserverAccount ob)
{
lObservers.Add(ob);
} public void RemoveObserver(IObserverAccount ob)
{
lObservers.Remove(ob);
} }
internal interface IObserverAccount
{
void Update();
}
class BankAccount:Subject
{
public BankAccount(double money)
: base(money)
{
}
}
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
internal class Emailer : Pattern_P16_行为型模式_观察者模式.模式4.IObserverAccount
{
private string _emailer;
private Subject _subject; public Emailer(string emailer, Subject subject)
{
this._emailer = emailer;
this._subject = subject;
} public void Update()
{
//..
Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emailer, _subject.Money);
} }
class Mobile : IObserverAccount
{
private long _phoneNumber;
public Subject _subject; public Mobile(long phoneNumber, Subject subject)
{
this._phoneNumber = phoneNumber;
this._subject = subject;
} public void Update()
{
Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _phoneNumber, _subject.Money);
}
}
此时客户端调用如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
////------------------------------ 模式4 ----------------------------------------
//model4.Subject subject = new model4.BankAccount(2000);
//model4.IObserverAccount emailer = new model4.Emailer("abcdwxc@163.com", subject);
//model4.IObserverAccount mobile = new model4.Mobile(13901234567, subject);
//subject.AddObserver(emailer);
//subject.AddObserver(mobile);
//subject.WithDraw();
////-------------------------------------------------------------------------------
.NET中Observer实现:
用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->
internal class Subject
{
public event DelegateClass.NotifyEventHander notifyEvent; private double _money; public double Money
{
get { return _money; }
set { _money = value; }
} public Subject(double money)
{
this._money = money;
} public void WithDraw()
{
OnNotifyChange();
} public void OnNotifyChange()
{
if (notifyEvent != null)
{
notifyEvent(this);
} }
}
class Emailer
{
private string _emailer; public Emailer(string emailer)
{
this._emailer = emailer;
} public void Update(object obj)
{
if (obj is Subject)
{
Subject subject = (Subject)obj; Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emailer, subject.Money);
}
} }
public delegate void NotifyEventHandler(object sender);
客户端调用如
客户端调用如下:
//------------------------------ 模式5 ----------------------------------------
model5.Subject subject = new model5.Subject();
model5.Emailer emailer = new model5.Emailer("abcdwxc@163.com");
model5.Mobile mobile = new model5.Mobile();
subject.notifyEvent += new model5.DelegateClass.NotifyEventHander(emailer.Update);
subject.notifyEvent += new model5.DelegateClass.NotifyEventHander(mobile.Update);
subject.WithDraw();
//-------------------------------------------------------------------------------
Observer实现要点:
1.使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
2.目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。
3.在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计。
16.观察者模式(Observer Pattern)的更多相关文章
- 设计模式 - 观察者模式(Observer Pattern) 详细说明
观察者模式(Observer Pattern) 详细说明 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26583157 版权全部 ...
- 乐在其中设计模式(C#) - 观察者模式(Observer Pattern)
原文:乐在其中设计模式(C#) - 观察者模式(Observer Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 观察者模式(Observer Pattern) 作者:weba ...
- 设计模式 - 观察者模式(Observer Pattern) 详细解释
观察者模式(Observer Pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26583157 版权全部 ...
- 设计模式-观察者模式(Observer Pattern)
观察者模式(Observer Pattern):定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己. 观察者 ...
- jQuery中的观察者模式(Observer Pattern)
在jQuery中,on方法可以为元素绑定事件,trigger方法可以手动触发事件,围绕这2个方法,我们来体验jQuery中的观察者模式(Observer Pattern). ■ on方法绑定内置事件, ...
- 设计模式 - 观察者模式(Observer Pattern) Java内置 用法
观察者模式(Observer Pattern) Java内置 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26601659 ...
- 二十四种设计模式:观察者模式(Observer Pattern)
观察者模式(Observer Pattern) 介绍定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新. 示例有一个Message实体类,某些对象 ...
- 使用C# (.NET Core) 实现观察者模式 (Observer Pattern) 并介绍 delegate 和 event
观察者模式 这里面综合了几本书的资料. 需求 有这么个项目: 需求是这样的: 一个气象站, 有三个传感器(温度, 湿度, 气压), 有一个WeatherData对象, 它能从气象站获得这三个数据. 还 ...
- php观察者模式(observer pattern)
... <?php /* The observer pattern implements a one-too-many dependency between objects. The objec ...
- C#设计模式——观察者模式(Observer Pattern)1
一.概述在软件设计工作中会存在对象之间的依赖关系,当某一对象发生变化时,所有依赖它的对象都需要得到通知.如果设计的不好,很容易造成对象之间的耦合度太高,难以应对变化.使用观察者模式可以降低对象之间的依 ...
随机推荐
- 数据库 -- mysql支持的数据类型
mysql支持的数据类型 数值类型 MySQL支持所有标准SQL数值数据类型. 这些类型包括严格数值数据类型(INTEGER.SMALLINT.DECIMAL和NUMERIC),以及近似数值数据类型( ...
- P1020 导弹拦截
思路:贪心思路 拿比飞来的导弹高并且高度和飞来的导弹最相近的拦截系统去接, 如果全部都比导弹矮,那就新开一个拦截系统 #include<cstdio> #include<string ...
- Codeforces519 E. A and B and Lecture Rooms
传送门:>Here< 题意:询问给出一棵无根树上任意两点$a,b$,求关于所有点$i$,$dist(a,i) = dist(b,i)$的点的数量.要求每一次询问在$O(log n)$的时间 ...
- 【题解】 AtCoder ARC 076 F - Exhausted? (霍尔定理+线段树)
题面 题目大意: 给你\(m\)张椅子,排成一行,告诉你\(n\)个人,每个人可以坐的座位为\([1,l]\bigcup[r,m]\),为了让所有人坐下,问至少还要加多少张椅子. Solution: ...
- 【BZOJ3691】游行(网络流)
[BZOJ3691]游行(网络流) 题面 BZOJ 然而权限题. Description 每年春季,在某岛屿上都会举行游行活动. 在这个岛屿上有N个城市,M条连接着城市的有向道路. 你要安排英雄们的巡 ...
- 【CF809C】Find a car(动态规划)
[CF809C]Find a car(动态规划) 题面 洛谷 CF 有一个无穷大的矩阵,第\(i\)行第\(j\)列的数是\((i-1)xor(j-1)+1\),\(q\)次询问,每次询问一个矩形内数 ...
- 子网站不继承父的WEBCONFIG
环境 W10 IIS10 / WIN2012 IIS上以前有一个网站,后来写了一个接口项目,需要当成WEB应用程序挂到这网站下. 在右击添加应用程序,指向接口项目后.发现访问不了接口项目.死活配置有 ...
- SElinux解决web网站无法访问
SElinux解决web网站无法访问工具/原料centos 6.5系统httpd web服务器 SELinux 设置为enforcing:强制模式,代表 SELinux 运作中 方法/步骤1. 1se ...
- 通俗讲解MOSFET
一位工程师曾经对我讲,他从来不看MOSFET数据表的第一页,因为“实用”的信息只在第二页以后才出现.事实上,MOSFET数据表上的每一页都包含有对设计者非常有价值的信息.但人们不是总能搞得清楚该如何解 ...
- 深入学习semaphore
深入学习semaphore 控制同时访问资源线程数 访问特定资源前,先使用acquire(1)获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可. 访问资源后,使用release()释放许可 ...