一、引言
相信猿友都大大小小经历过一些面试,其中有道经典题目,场景是猫咪叫了一声,老鼠跑了,主人被惊醒(设计有扩展性的可加分)。对于初学者来说,可能一脸懵逼,这啥跟啥啊是,其实博主当年也这感觉,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,这下面试的时候再也不怕面试官问你观察者模式相关知识了吧。。。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。