一、定义
观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式是写松耦合代码的必备模式。
二、结构
三、实现
//被观察者 public interface IObject { IList<IMonitor> ListMonitor { get; set; } //定义观察者集合,因为多个观察者观察一个对象,所以这里用集合 string SubjectState { get; set; } //被观察者的状态 void AddMonitor(IMonitor monitor); //添加一个观察者 void RemoveMonitor(IMonitor monitor); //移除一个观察者 void SendMessage(); //向所有观察者发送消息 } public class Subject : IObject { private IList<IMonitor> listMonitor = new List<IMonitor>(); public string SubjectState //被观察者的状态 { get;set; } public IList<IMonitor> ListMonitor //实现具体的观察者列表属性 { get { return listMonitor; } set { listMonitor = value; } } public void AddMonitor(IMonitor monitor) //实现具体的添加观察者方法 { listMonitor.Add(monitor); } public void RemoveMonitor(IMonitor monitor) //实现具体的移除观察者方法 { listMonitor.Remove(monitor); } public void SendMessage() //实现具体的发送消息方法 { foreach (IMonitor m in listMonitor) //发送给所有添加过的观察者,让观察者执行update方法以同步更新自身状态 { m.Update(); } } } //观察者 public interface IMonitor //定义观察者接口 { void Update(); } public class Monitor : IMonitor //实现具体观察者 { private string monitorState="Stop!"; //观察者初始状态,会随着被观察者变化而变化 private string name; //观察者名称,用于标记不同观察者 private IObject subject; //被观察者对象 public Monitor (IObject subject, string name) //在构造观察者时,传入被观察者对象,以及标识该观察者名称 { this.subject = subject; this.name = name; Console.WriteLine("我是观察者{0},我的初始状态是{1}", name, monitorState); } public void Update() //当被观察者状态改变,观察者需要随之改变 { monitorState = subject.SubjectState; Console.WriteLine("我是观察者{0},我的状态是{1}", name, monitorState); } } //前端调用 static void Main(string[] args) { IObject subject = new Subject(); subject.AddMonitor(new Monitor(subject, "Monitor_1")); subject.AddMonitor(new Monitor(subject, "Monitor_2")); subject.AddMonitor(new Monitor(subject, "Monitor_3")); subject.SubjectState = "Start!"; subject.SendMessage(); Console.Read(); } }
四、适用场景
1 当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。
2 一个抽象某型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
五、优缺点
优点
观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
缺点
依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。
六、模式引申
应用C#中的事件委托来彻底解除通知者和观察者之间的耦合。
1 关于委托的定义:委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法有相同的行为。委托方法可以像其它任何方法一样,具有参数和返回值。委托可以看作是对函数(方法)的的抽象,是函数的“类”,委托的实例代表一个(或多个)具体的函数,它可以是多播的。
2 关于事件:事件基于委托,为委托提供了一种发布/订阅机制。事件的订阅与取消与我们刚才讲的观察者模式中的订阅与取消类似,只是表现形式有所不同。在观察者模式中,订阅使用方法Attach()来进行;在事件的订阅中使用“+=”。类似地,取消订阅在观察者模式中用Dettach(),而事件的取消用“-=”。
七、答疑
1. 在普通的观察者模式中,解耦并不彻底,那么在事件的发布订阅模型中,解耦彻底吗?为什么?
答案是肯定的。因为在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题。这也是关键点之四
2. 通过委托绑定方法来实现观察者模式,会不会有什么隐患?
有的,通过+=去把方法绑定到委托,很容易忘记-=。如果只绑定不移除,这个方法会一直被引用。我们知道GC去回收的时候,只会处理没有被引用的对象,只要是还被引用的对象时不会被回收掉的。所以如果在长期不关闭的系统中(比如监控系统),大量的代码使用+=而不-=,运行时间长以后有可能会内存溢出。
3. 事件,委托,观察者模式之间的关系
这个上面已经提到了,委托时一种类型,事件是一种特殊的委托,观察者模式是一种设计模式,事件的机制是观察者模式的一种实现,其中订阅者和发布者通过委托实现协同工作。
好的,最后我们还是来归纳一下观察者模式的关键点
一、每个观察者需要被保存到被观察者的集合中,并且被观察者提供添加和删除的方式。
二、被观察者把自己传给观察者,当状态改变后,通过遍历或循环的方式逐个通知列表中的观察者。
三、虽然解耦了观察者和被观察者的依赖,让各自的变化不大影响另一方的变化,但是这种解耦并不是很彻底,没有完全解除两者之间的耦合。
四、在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题
注意点:在使用委托绑定方法时,需要注意移除方法,否则可能会造成内存溢出。
建议:不能理解事件和委托的同学,好好地把事件和委托看一看,并且自己写点代码加深印象。
参考:
http://blog.jobbole.com/109845/
http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html
欢迎阅读本系列文章:Head First设计模式之目录