C#设计模式(17)——观察者模式
出处:https://www.cnblogs.com/wyy1234/
阅读目录
1.观察者模式介绍
观察者模式又叫发布-订阅模式,它定义了对象间的一种一对多关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并被自动更新。观察者模式就四个角色:抽象主题,具体主题,抽象观察者,具体观察者。抽象主题是一个抽象的接口或者抽象类,对主题的功能进行抽象,抽象观察者对具体的观察者进行抽象。观察者模式在软件开发中的应用十分广泛,如微信订阅号、微博订阅等都采用了观察者模式,我们关注了某个明星的微博,当这个明星更新微博状态时我们的微博都会收到通知,明星的微博就是主题角色,我们的微博属于观察者角色。
下边通过大话设计模式中秘书做卧底的例子来理解观察者模式的用法。上班时间有的同事喜欢偷偷看股票行情,有的同事看NBA直播,但是老板会不定时来办公室,如果被老板逮到就不好了,于是大家想出来一个办法:让秘书小妹在外边把风,如果老板来了就通知下大家,这样就不会被逮到了。这个栗子中秘书小妹就是一个主题,同事们属于观察者,秘书小妹如果看到老板过来就会通知 送她零食的同事们。代码比较简单:
//抽象主题类 public interface ISubject { //添加观察者 送零食的加进来,老板来了通知你 void Add(Observer observer); //删除观察者 不送零食的秘书小妹就不通知了 void Remove(Observer observer); //主题状态 string SubjectState { get; set; } //通知方法 void Notify(); } //具体主题 ,秘书类 public class Mishu : ISubject { //秘书要知道通知哪些同事 private IList<Observer> observers = new List<Observer>(); public void Add(Observer observer) { observers.Add(observer); } public void Remove(Observer observer) { observers.Remove(observer); } public string SubjectState { get; set; } public void Notify() { foreach (Observer o in observers) { o.Update(); } } } //抽象观察者 public abstract class Observer { //名字 protected string name; //观察者要知道自己订阅了那个主题 protected ISubject sub; public Observer(string name, ISubject sub) { this.name = name; this.sub = sub; } //接受到通知后的更新方法 public abstract void Update(); } //看股票的同事 public class StockObserver : Observer { public StockObserver(string name, ISubject sub) : base(name, sub) { } public override void Update() { Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭股票行情,继续工作!"); } } //看NBA的同事 public class NBAObserver : Observer { public NBAObserver(string name, ISubject sub) : base(name, sub) { } public override void Update() { Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭NBA直播,继续工作!"); } } /// <summary> /// 客户端调用 /// </summary> class Program { static void Main(string[] args) { Mishu mishu = new Mishu(); //新建同事 观察者角色 Observer tongshi1 = new StockObserver("巴菲特", mishu); Observer tongshi2 = new NBAObserver("麦迪", mishu); //秘书小妹要知道哪些同事要通知(主题要知道所有订阅了自己的观察者) mishu.Add(tongshi1); mishu.Add(tongshi2); //主题状态更改了 mishu.SubjectState = "老板回来了!"; //调用主题的通知方法 mishu.Notify(); Console.ReadKey(); } }
运行程序,执行结果如下:
上边的例子中秘书小妹充当主题角色,上班偷懒的同事充当观察者角色,通过观察者模式实现了功能。但是这里还有一个小问题:上边例子能够成功的前提是所有观察者的角色都有一个Update()方法来执行更新。但是有时候各个观察者并不都是相同的类型,如观察者1收到通知执行Update1()方法,而观察者2收到通知执行的是Update2()方法,这时候采用上边的模式就不能满足需求了。怎么改进呢?①各个观察者不属于同一类,所以不需要抽象观察者类了 ②因为各个观察者的反应不是同一的Update(),所以我们不能foreach遍历观察者集合来统一调用Update()方法了,这时可以考虑通过事件委托在客户端确定所有观察者的反应。改进后的代码如下:
//抽象主题角色 public interface ISubject { //添加观察者 void Add(Observer observer); //删除观察者 void Remove(Observer observer); //主题状态 string SubjectState { get; set; } //通知方法 void Notify(); } //***********************1.定义一个委托 public delegate void EventHandler();
//具体主题角色 秘书类 public class Mishu : ISubject { public event EventHandler Update; //存储要通知的同事 public IList<Observer> observers = new List<Observer>(); public string SubjectState { get; set; } public void Add(Observer observer) { observers.Add(observer); } public void Remove(Observer observer) { observers.Remove(observer); } //**********2.通知方法中不能通过遍历来统一调用每个观察者的Update()方法了,改成执行一个委托 public void Notify() { Update(); } } //抽象观察者角色 public abstract class Observer { protected ISubject sub; protected string name; protected Observer(string name,ISubject sub) { this.name = name; this.sub = sub; } } //具体观察者角色 看股票的同事 public class StockObserver { public string name; public ISubject sub; public StockObserver(string name,ISubject sub) { this.name = name; this.sub = sub; } public void CloseStockMarket() { Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭股票行情,继续工作!"); } } //具体观察者角色 看NBA的同事 public class NBAObserver { public string name; public ISubject sub; public NBAObserver(string name, ISubject sub) { this.name = name; this.sub = sub; } public void CloseNBA() { Console.WriteLine($"通知内容:{sub.SubjectState},反应:{name}关闭NBA直播,继续工作!"); } } class Program { static void Main(string[] args) { Mishu mishu = new Mishu(); //观察者订阅了主题mishu StockObserver tongshi1 = new StockObserver("巴菲特", mishu); NBAObserver tongshi2 = new NBAObserver("麦迪", mishu); //*******************3.将遍历观察者并调用观察者的Update(),改成了事件委托形式 mishu.Update += tongshi1.CloseStockMarket; mishu.Update += tongshi2.CloseNBA; //主题状态更改,并通知 mishu.SubjectState = "老板回来了!"; mishu.Notify(); Console.ReadKey(); } }
运行结果:
我们看到通过事件委托我们可以实现让不同的观察者调用不同的方法,当我们遇到类似这样的情况:点击一个按钮,有的页面元素显示Show(),有的隐藏Hide(),有的关闭Close()就可以采用这种模式。但是这种模式没有对具体的观察者进行抽象,如果观察者太多也会造成事件委托过于复杂。两种观察者模式的实现各有利弊,我们可以根据实际的情况来选择。
2.小结
上边栗子的类图:
观察者模式的使用场景:当一个对象的状态发生变化时,需要让其它对象知道并作出反应可以考虑观察者模式。
观察者模式的优点:想一下如果不使用观察者模式,订阅者怎么获取订阅号的更新?最直接的方法应该就是轮询了,这种方式十分浪费资源,而且获取更新也不及时。观察者模式的主要功能就是解决了这一问题。这种模式符合依赖倒置原则,同时降低了观察者和主题间的耦合,建立了一套触发机制。