一、引言
相信猿友都大大小小经历过一些面试,其中有道经典题目,场景是猫咪叫了一声,老鼠跑了,主人被惊醒(设计有扩展性的可加分)。对于初学者来说,可能一脸懵逼,这啥跟啥啊是,其实博主当年也这感觉,O(∩_∩)O哈哈~好了,废话不多说,今天我们要学习的内容就是要解决这种业务场景——观察者模式,又叫发布-订阅(Publish/Subscrible)模式
二、观察者模式
定义:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象状态发生变化时,会通知所有观察者对象,使它们能够自行更新自己
下面是观察者模式结构图:
该图示出自“大话设计模式”
下面通过大家都熟悉的生活场景来帮助我们一步步了解观察者模式
场景:上自习课的时候,困了想睡觉,通常是会跟同桌说:“老师来了叫我下,我睡会”。(大多数人都是这样吧,嘿嘿)
下面是代码demo:

//主题类 class ConcreteSubject { private IList<ConcreteObserver> lstConcreteObserver = new List<ConcreteObserver>(); private string action; //添加观察者 public void Add(ConcreteObserver concreteObserver) { lstConcreteObserver.Add(concreteObserver); } //移除观察者 public void Remove(ConcreteObserver concreteObserver) { lstConcreteObserver.Remove(concreteObserver); } //通知观察者类 public void Notify() { foreach (ConcreteObserver observer in lstConcreteObserver) { observer.Update(); } } //定义主题发现的某一状态 public string ConcreteAction { get { return action; } set { action = value; } } } //观察者类 class ConcreteObserver { protected ConcreteSubject subject; protected string name; public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } //观察者更新行为 public void Update() { Console.WriteLine($"{subject.ConcreteAction},{name},别睡觉了,快醒醒!"); } } static void Main(string[] args) { ConcreteSubject subject = new ConcreteSubject(); ConcreteObserver observer = new ConcreteObserver(subject, "michael"); subject.Add(observer); subject.ConcreteAction = "老师来了"; subject.Notify(); Console.Read(); }
分析:乍一看是写的不错,实现了老师来时同桌通知michael,但是仔细想一下,假如现在情况变了,同桌另一边的同学在打游戏,也让老师来时通知一下,怎么办?这时我们就需要去修改ConcreteSubject类中对观察者ConcreteObserver的引用,还有另外一种情况,假如某天同桌请假了,通常会让前后排的同学通知观察者,即修改观察者类ConcreteObserver中对ConcreteSubject的引用。这种相互耦合的设计显然是不太好,当场景变了的时候,不得不去修改原有代码,违背了开放-封闭原则。
下面看一下大话设计模式中的例子是如何介绍观察者模式的:

//抽象观察者类 abstract class Observer { public abstract void Update(); } //抽象主题类 abstract class Subject { private IList<Observer> lstConcreteObserver = new List<Observer>(); public void Add(Observer concreteObserver) { lstConcreteObserver.Add(concreteObserver); } public void Remove(Observer concreteObserver) { lstConcreteObserver.Remove(concreteObserver); } public void Notify() { foreach (Observer observer in lstConcreteObserver) { observer.Update(); } } } //具体观察者类 class ConcreteObserver:Observer { protected ConcreteSubject subject; protected string name; private string observerState; public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } public override void Update() { this.observerState = subject.ConcreteAction; Console.WriteLine($"the observer's of {name} state is {observerState}"); } } //具体主题对象 class ConcreteSubject:Subject { private string action; public string ConcreteAction { get { return action; } set { action = value; } } } static void Main(string[] args) { ConcreteSubject subject = new ConcreteSubject(); subject.Add(new ConcreteObserver(subject, "michael")); subject.Add(new ConcreteObserver(subject, "jarle")); subject.Add(new ConcreteObserver(subject, "cumming")); subject.ConcreteAction = "Ready"; subject.Notify(); Console.Read(); }
分析:这次是不是可扩展性更高了?嗯,是的。观察者与主题对象都依赖于抽象,而不依赖与具体,使得各自变化都不会影响到另一边
其实现在已经很好了,但是这样具体的观察者都要依赖于观察者的抽象,那能不能再进行优化一次呢?答案是完全可以的。
下面我们用委托事件来解决开头那个考烂了的面试题,解释如何优化的
委托:是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用

class Mao { private string name; public delegate void MaoDelegateHandler(); public event MaoDelegateHandler MaoEventHandler; public Mao(string name) { this.name = name; } public void Miao() { Console.WriteLine($"{this.name}叫了一声"); Notify(); } public void Notify() { if (MaoEventHandler != null) MaoEventHandler(); } public string Name { get { return name; } set { name = value; } } } class Laoshu { public void Run() { Console.WriteLine("老鼠逃跑了"); } } class People { public void Wake() { Console.WriteLine("主人被惊醒了"); } } class Program { static void Main(string[] args) { Mao mao = new Mao("大脸猫"); Laoshu laoshu = new Laoshu(); People people = new People(); //注册事件 这两种方式都可以 mao.MaoEventHandler += new Mao.MaoDelegateHandler(laoshu.Run); mao.MaoEventHandler += people.Wake; //猫叫了一声 会自动调用注册过的方法 mao.Miao(); Console.Read(); } }
分析:用委托事件来实现,发布者和订阅者之间没有耦合,是不是有优化了一步呢?O(∩_∩)O~
优点:
1.观察者模式解除了发布者和订阅者的耦合,两者都依赖于抽象,而不是具体的,使得两者可以各自独立的变化
缺点:
1.观察者对象如果很多的话,被观察者通知会耗时增多
2.在被观察者之间如果有相互依赖的话,会相互调用,导致系统崩溃(小白注意)
适用场景:
1.当一个对象改变需要改变其它对象时,而且不知道有多少个对象需要改变
2.当一个抽象模型有两个方面,一个方面依赖于另一个方面,将两者封装在独立的对象中以使它们可以各自独立的变化和复用
介绍到这里其实观察这模式已经结束了,但是我觉得很多小白搞不懂winform开发程序中的grid单击、双击等事件,方法后面的参数sender和e分别是什么。。。下面再简要分析一下

class Mao { private string name; //声明委托 public delegate void MaoDelegateHandler(object sender, MaoEventArgs e); //声明事件 public event MaoDelegateHandler MaoEventHandler; public class MaoEventArgs : EventArgs { private string name; public MaoEventArgs(string name) { this.name = name; } } public Mao(string name) { this.name = name; } //发布者 某一行为后 发出通知 public void Miao() { Console.WriteLine($"{this.name}叫了一声"); //建立MaoEventArgs对象 MaoEventArgs e = new MaoEventArgs(this.Name); //调用通知方法 Notify(e); } public void Notify(MaoEventArgs e) { //如果有订阅者注册 if (MaoEventHandler != null) //执行所有注册的方法 MaoEventHandler(this,e); } public string Name { get { return name; } set { name = value; } } } class Laoshu { //是不是很熟悉,请自行脑补winfom中grid的事件及sender和e是什么 public void Run(object sender,Mao.MaoEventArgs e) { Mao mao = (sender as Mao); Console.WriteLine($"{mao.Name}来了,老鼠逃跑了"); } } class People { public void Wake(object sender, Mao.MaoEventArgs e) { Mao mao = (sender as Mao); Console.WriteLine($"{mao.Name}的主人被惊醒了"); } } class Program { static void Main(string[] args) { Mao mao = new Mao("大脸猫"); Laoshu laoshu = new Laoshu(); People people = new People(); //注册事件 这两种方式都可以 mao.MaoEventHandler += new Mao.MaoDelegateHandler(laoshu.Run); mao.MaoEventHandler += people.Wake; //猫叫了一声 会自动调用注册过的方法 mao.Miao(); Console.Read(); } }
分析:ok,这下面试的时候再也不怕面试官问你观察者模式相关知识了吧。。。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。