本系列:
上一篇中,我给大家详细讲解了一下工厂模式以及面试中可能会被问到的关键点,我们先来温习温习。
工厂模式的关键点:
一、工厂模式分为简单工厂,工厂和抽象工厂
二、三种工厂的实现是越来越复杂的
三、简单工厂通过构造时传入的标识来生产产品,不同产品都在同一个工厂中生产,这种判断会随着产品的增加而增加,给扩展和维护带来麻烦
四、工厂模式无法解决产品族和产品等级结构的问题
五、抽象工厂模式中,一个工厂生产多个产品,它们是一个产品族,不同的产品族的产品派生于不同的抽象产品(或产品接口)。
另外,如果面试官问你在项目中有没有用过,千万千万不要再举连接数据库的例子了,真的。。。
好了,温习完了以后,我们下面来讲讲今天的内容,观察者模式。
单例,工厂,观察者,我认为是设计模式三杰,为何这么说,因为这三种设计模式使用频率最高,变化最多,而且覆盖面最广,当然,也是面试中最容易被问到的。当然,像装饰,建造,策略这几个也是经常会用到的,后面文章中我会一一展开讨论。
从这里开始,我们在介绍设计模式之前,需要强调一下角色。实际上从上篇工厂模式中,就已经有角色的概念。大家想想,工厂模式中有哪些角色?没错,就是工厂角色和产品角色。同样,在观察者模式中,也有两个角色,就是观察者和被观察者。在理解设计模式的时候,首先要有个概念,就是每个角色都对应这一个类,比如观察者模式,观察者肯定对应着一个观察者类,被观察者肯定对应的被观察者类。那么设计模式实际上就是通过面向对象的特性,将这些角色解耦。
观察者模式本质上就是一种订阅/发布的模型,从逻辑上来说就是一对多的依赖关系。什么意思呢?好比是一群守卫盯着一个囚犯,只要囚犯一有异动,守卫就必须马上采取行动(也有可能是更新状态,本质上也是一种行动),那么守卫就是观察者,囚犯就是被观察者。
在一个系统中,实现这种一对多的而且之间有一定关联的逻辑的时候,由于需要保持他们之间的协同关系,所以最简便的方法是采用紧耦合,把这些对象绑定到一起。但是这样一来,一旦有扩展或者修改的时候,开发人员所面对的难度非常大,而且很容易造成Bug。那么观察者模式就解决了这么一个问题,在保持一系列观察者和被观察者对象协同工作的同时,把之间解耦了。
好了,废话不多,撸代码:
//被观察者 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(); } }
结果如下
我是观察者Monitor_1,我的初始状态是Stop! 我是观察者Monitor_2,我的初始状态是Stop! 我是观察者Monitor_3,我的初始状态是Stop! 我是观察者Monitor_1,我的状态是Start! 我是观察者Monitor_2,我的状态是Start! 我是观察者Monitor_3,我的状态是Start!
这样就完成了一个观察者模式。我们回过头来看看,在被观察者中,我定义了一个集合用来存放观察者,并且我写了一个Add方法一个Remove方法来添加和移除观察者,这体现了一对多的关系,也提供了可以控制观察者的方式。所以,我们得到第一个关键点:每个观察者需要被保存到被观察者的集合中,并且被观察者提供添加和删除的方式。
然后我么再看一下,观察者和被观察者之间的交互活动。不难发现,我是在添加一个观察者的时候,把被观察者对象以构造函数的形式给传入了观察者。最后我让被观察者执行sendmessage方法,这时会触法所有观察着的update方法以更新状态。所以我们得到第二个关键点,被观察者把自己传给观察者,当状态改变后,通过遍历或循环的方式逐个通知列表中的观察者。
好了,到这里你应该可以把握住观察者模式的关键了。但这里有个问题,被观察者是通过构造函数参数的形式,传给观察者的,而观察者对象时被Add到被观察者的List中。所以,我们得到第三个关键点,虽然解耦了观察者和被观察者的依赖,让各自的变化不大影响另一方的变化,但是这种解耦并不是很彻底,没有完全解除两者之间的耦合。
有很多同学比较怕被问到观察者模式,特别是搞.Net的同学。为什么呢?因为一旦涉及到观察者模式,必然会涉及到2中类型,委托和事件。因为很多人并不理解委托和事件,或者只停留在潜层次。那么,委托,事件,和观察者模式到底有什么关系呢?
首先我们来看委托。委托说白了,就是可以把方法当做另一个方法参数来传递的东东,当然方法签名需要注意一下。委托可以看做是方法的抽象,也就是方法的“类”,一个委托的实例可以是一个或者多个方法。我们可以通过+=或者-=把方法绑定到委托或者从委托移除。
再来看事件,实际上事件是一种特殊的委托。怎么说呢,首先事件也是委托,只是在声明事件的时候,需要加上event,如果你用reflector去看一个事件,你会发现里面就3样东西,一个Add_xxxx方法,一个Remove_xxx方法,一个委托。说道这里,是不是觉得和上面我们定义被观察者时的Add方法,Remove方法有些联系?
没错,实际上.Net的事件机制就是观察者模式的一种体现,并且是利用委托来实现。本质上事件就是一种订阅-发布模型也就是观察者模式,这种机制中包含2个角色,一个是发布者,一个是订阅者。发布者类也就类似于被观察者,发布者类包含事件和委托定义,以及其之间的关系,发布者类的对象调用事件通知其他订阅者。而订阅者类也就类似于观察者,观察者接受事件,并且提供处理的逻辑。
也就是说,订阅者对象(观察者)中的方法会绑定到发布者(被观察者)对象的委托中,一旦发布者(被观察者)中事件被调用,发布者(被观察者)就会调用委托中绑定的订阅者(观察者)的处理逻辑或者说是处理程序,这就是通过观察者模式实现的事件,虽然这段话比较拗口,但是我想理解起来应该还是不太难把。。。
好了,你虽然现在已经对面试官逼逼叨了上面一大通,但是贱贱的面试官仍然想challenge你一下,否则不就是太没面子么。。。
答案是肯定的。因为在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题。这也是关键点之四
2. 通过委托绑定方法来实现观察者模式,会不会有什么隐患?
有的,通过+=去把方法绑定到委托,很容易忘记-=。如果只绑定不移除,这个方法会一直被引用。我们知道GC去回收的时候,只会处理没有被引用的对象,只要是还被引用的对象时不会被回收掉的。所以如果在长期不关闭的系统中(比如监控系统),大量的代码使用+=而不-=,运行时间长以后有可能会内存溢出。
3. 事件,委托,观察者模式之间的关系
这个上面已经提到了,委托时一种类型,事件是一种特殊的委托,观察者模式是一种设计模式,事件的机制是观察者模式的一种实现,其中订阅者和发布者通过委托实现协同工作。
好的,最后我们还是来归纳一下观察者模式的关键点
一、每个观察者需要被保存到被观察者的集合中,并且被观察者提供添加和删除的方式。
二、被观察者把自己传给观察者,当状态改变后,通过遍历或循环的方式逐个通知列表中的观察者。
三、虽然解耦了观察者和被观察者的依赖,让各自的变化不大影响另一方的变化,但是这种解耦并不是很彻底,没有完全解除两者之间的耦合。
四、在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题
注意点:在使用委托绑定方法时,需要注意移除方法,否则可能会造成内存溢出。
建议:不能理解事件和委托的同学,好好地把事件和委托看一看,并且自己写点代码加深印象。