前言
观察者模式又称发布(Publish) / 订阅(Subscribe)模式,对象行为型模式的一种。在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并被自动更新 ”。
场景
观察者模式,顾名思义就是存在一个观察者角色,它时刻观察着另一个目标角色,当这个目标角色做出观察者感兴趣的动作后,观察者会相应的做出其它的操作。类似观察者模式的场景有很多。比如在微博中,当一个用户关注某人时,这个人发布新的微博后用户会得到通知。在这里,用户扮演的就是观察者角色,而被关注的人扮演的就是目标角色。当目标角色发布新微博(用户感兴趣的动作)后会通知那些关注他的用户,用户收到通知后,会查看这条微博。
结构
- Subject(目标抽象):所有目标类的接口。可有可无,因为很难保证所有被观察的目标都实现该接口;
- ConcreteSubject(具体目标):维护了一个观察者列表,并且提供注册和删除观察者的方法。当它本身做出了观察者感兴趣的动作后,向观察者发送通知;
- Observer(观察者抽象):用来保证观察者的一致性,为那些在目标做出动作时需要获得通知的对象定义一个接收接口;
- ConcreteObserver(具体观察者):维护一个指向ConcreteSubject对象的引用,并实现Observer;
示例
在下面代码中,我们将模拟场景中提到的微博推送功能。
public class Star { public List<IFans> FansList { set; get; } = new List<IFans>(); public string Msg { set; get; } = string.Empty; public void CreateWeibo() { this.Msg = "今天天气不错!"; Console.WriteLine($"[Star]:发布了一条微博 - {this.Msg}"); this.Notify(); } public void Notify() { Console.WriteLine("[Star]:向我的粉丝发送通知"); foreach (var fans in FansList) { fans.CheckWeibo(); } } } public interface IFans { Star Idol { set; get; } void CheckWeibo(); } public class FansA : IFans { public FansA(Star star) { star.FansList.Add(this); this.Idol = star; } public Star Idol { get; set; } public void CheckWeibo() { Console.WriteLine($"[FansA]: 查看明星Star发布的微博 - {this.Idol.Msg}"); } } public class FansB : IFans { public FansB(Star star) { star.FansList.Add(this); this.Idol = star; } public Star Idol { get; set; } public void CheckWeibo() { Console.WriteLine($"[FansB]: 查看明星Star发布的微博 - {this.Idol.Msg}"); } } static void Main(string[] args) { Star star = new Star(); IFans fandA = new FansA(star); IFans fansB = new FansB(star); star.CreateWeibo(); Console.ReadKey(); }
在示例中,我们通过FanA/B的构造函数将其感兴趣的Star对象注入到该类中并将该类注册到Star对象中以观察Star对象的Notify动作。当Star类发生CreateWeibo操作时,Star类将通过Notify函数调用其所有观察者的CheckWeibo函数。在观察者的CheckWeibo函数中,通过其自身保留的Star引用查看它关心的信息。
在观察者模式中Notify可由目标类本身触发或由用户触发。
- 由目标类触发Notify时,可以保证目标类的每次动作都能够被观察者发现;
- 由用户触发Notify时,可以保证在目标类频繁做出动作而观察者只关心最后一个动作时的效率。即在目标类完成所有动作后,用户在触发Notify通知观察者;
同时,观察者所接收的目标类的信息(Star类中的Msg)也分为推、拉两种模式。
- 推模式,由目标类向观察者发送信息,比如在Start类中调用CheckWeibo函数时,将Msg以入参的方式传给IFans。好处是不需要保留目标类的引用,但并不是所有观察者都需要这个Msg;
- 拉模式,由观察者通过目标类的引用获取Msg,如示例。好处是更加灵活,坏处是需要保留目标类的引用;
补充
观察者模式 + 中介者模式
在观察者模式中存在一个Changemanager(更改管理器),它是帮助观察者与目标保持一个松耦合的状态。虽然多态保证了目标类支持所有实现了某个接口的类,但并不是所有的观察者都实现了该接口。而ChangeManager能够帮助目标支持多种类型的观察者(帮助目标维护其观察者列表,目标类通过ChangeManager向观察者发送通知),并且ChangeManager中可以定义特定的通知策略(比如目标做完一系列动作后再通知观察者)。
ChangeManager是一个中介者模式(Mediator Pattern)的实例。
观察者模式在MVC中的应用
观察者模式最经典的应用莫过于MVC中的Model和View。
我们都知道在MVC(Model-模型,View-视图,Controller-控制器)中一个View的展示需要Model的支撑,View随着Model的变更而变更。也就是说Model代表的是目标角色,而View则代表了观察者。Model发生变更时通过Controller通知View,View收到通知后作出相应动作,这也正是观察者模式+中介者模式的应用。
总结
观察者模式帮助我们实现了一套触发机制,并且能够将目标与观察者解耦(因为目标类并不关心也不知道具体的观察者类)。需要注意的是,目标在通知观察者时需要谨慎处理,避免在发生异常时没有将后续的观察者通知到(可采用异步方式)。
以上,就是我对观察者模式的理解,希望对你有所帮助。
示例源码:https://gitee.com/wxingChen/DesignPatternsPractice
系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html
本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10124664.html)