目录
1. 观察者模式的定义
2. 观察者模式的结构
3. 在观察者模式的结构图有以下角色
4. 观察者模式的实现
5. 观察者模式的适用场景
6. 观察者模式的优缺点
7. 实现思路
在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用
1. 观察者模式的定义
从生活中的例子可以看出,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。从中,我们就可以得出观察者模式的定义。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。
2. 观察者模式的结构
从上面观察者模式的定义和生活中的例子,很容易知道,观察者模式中首先会存在两个对象,一个是观察者对象,另一个就是主题对象,然而,根据面向接口编程的原则,则自然就有抽象主题角色和抽象观察者角色。
理清楚了观察者模式中涉及的角色后,接下来就要理清他们之间的关联了,要想主题对象状态发生改变时,能通知到所有观察者角色,则自然主题角色必须所有观察者的引用,这样才能在自己状态改变时,通知到所有观察者。
3. 在观察者模式的结构图有以下角色
抽象主题角色(Subject):抽象主题把所有观察者对象的引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做抽象被观察者角色,一般由抽象类或接口实现。
抽象观察者角色(Observer):为所有具体观察者定义一个接口,在得到主题通知时更新自己,一般由抽象类或接口实现。
具体主题角色(ConcreteSubject):实现抽象主题接口,具体主题角色又叫做具体被观察者角色。
具体观察者角色(ConcreteObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调。
4. 观察者模式的实现
下面以微信订阅号的例子来说明观察者模式的实现。现在要实现监控腾讯游戏订阅号的状态的变化。这里一开始不采用观察者模式来实现,而通过一步步重构的方式,最终重构为观察者模式。
因为一开始拿到需求,自然想到有两个类,一个是腾讯游戏订阅号类,另一个是订阅者类。订阅号类中必须引用一个订阅者对象,这样才能在订阅号状态改变时,调用这个订阅者对象的方法来通知到订阅者对象。(ObserverPattern.cs)
上面称为方法1:确实实现了监控订阅号的任务。但这里的实现存在下面几个问题:
TenxunGame类和Subscriber类之间形成了一种双向依赖关系,即TenxunGame调用了Subscriber的ReceiveAndPrintData方法,而Subscriber调用了TenxunGame类的属性。
这样的实现,如果有其中一个类变化将引起另一个类的改变。当出现一个新的订阅者时,此时不得不修改TenxunGame代码,即添加另一个订阅者的引用和在Update方法中调用另一个订阅者的方法。违背了“开放——封闭”原则
方法2:(ObserverPattern2.cs)
对此我们要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体的订阅者之间的依赖,做这样一步改进,确实可以解决TenxunGame类与具体订阅者之间的依赖,使其依赖与接口,从而形成弱引用关系,
但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。对此,我们可以做这样的思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,
所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类
方法3:(ObserverPattern3.cs)
在.NET中,我们可以使用委托与事件来简化观察者模式的实现
委托的使用方法
1)定义委托 (参数和返回值和Multiply一致)
delegate double processDelegate(double db1, double db2);
2)声明实例化(将函数和委托关联:Multiply是实际函数)
processDelegate process = new processDelegate(Multiply);
3)通过委托调用函数(实际是调用Multiply方法)
process(1,2)
5. 观察者模式的适用场景
1)当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用的情况下。
从方面的这个词中可以想到,观察者模式肯定在AOP(面向方面编程)中有所体现
2)当对一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下。
3)当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下。
6. 观察者模式的优缺点
优点:
1)观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
2)观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
3)观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。
缺点:
1)如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
2)虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
3)如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。
7. 实现思路
观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听某一个主题对象,这个主题对象在发生状态变化时,会通知所有观察者对象,使它们能够自动更新自己,
解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。大家可以以微信订阅号的例子来理解观察者模式。
个人理解(要想主题对象状态发生改变时,能通知到所有观察者角色(一对多),则自然主题角色必须所有观察者的引用):
1、创建观察者接口(委托充当订阅者接口)
public delegate void NotifyEventHandler(object sender);
2、创建主题类(也可以先创建一个抽象类,再继承它做个实现类,主题(订阅号)只有一个,所以不使用抽象类)
public class TenXun3
-> public void AddObserver(NotifyEventHandler ob) //新增订阅了该订阅号的订阅者的方法
-> NotifyEvent += ob;
-> public void RemoveObserver(NotifyEventHandler ob) //删除
-> NotifyEvent -= ob;
-> public void Update() //通知订阅者
-> NotifyEvent(this); //※委托列表的执行(不使用委托,就需要循环多个观察者,调用观察者的ReceiveAndPrint方法)
3、创建观察者类(订阅者类)
public class Subscriber3
-> public void ReceiveAndPrint(Object obj)
-> TenXun3 tenxun = obj as TenXun3;
tenxun.Symbol, tenxun.Info
4、客户端
//new订阅号和2个订阅者
TenXun3 tenXun3 = new TenXunGame3("TenXun Game", "Have a new game published ....");
Subscriber3 lh = new Subscriber3("Learning Hard");
Subscriber3 tom = new Subscriber3("Tom");
//添加订阅者(将订阅者的方法添加到委托)
tenXun3.AddObserver(new NotifyEventHandler(lh.ReceiveAndPrint));
tenXun3.AddObserver(new NotifyEventHandler(tom.ReceiveAndPrint));
//通过NotifyEvent += ob; 形成使用委托链 ,可以顺序调用各个委托
tenXun3.Update();
✳详细的代码实现,请点击资源绑定