面向对象编程思想-观察者模式

一、引言

相信猿友都大大小小经历过一些面试,其中有道经典题目,场景是猫咪叫了一声,老鼠跑了,主人被惊醒(设计有扩展性的可加分)。对于初学者来说,可能一脸懵逼,这啥跟啥啊是,其实博主当年也这感觉,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();
        }
View Code

分析:乍一看是写的不错,实现了老师来时同桌通知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();
        }
View Code

 分析:这次是不是可扩展性更高了?嗯,是的。观察者与主题对象都依赖于抽象,而不依赖与具体,使得各自变化都不会影响到另一边

其实现在已经很好了,但是这样具体的观察者都要依赖于观察者的抽象,那能不能再进行优化一次呢?答案是完全可以的。

下面我们用委托事件来解决开头那个考烂了的面试题,解释如何优化的

委托:是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用

    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();
        }
    }
View Code

分析:用委托事件来实现,发布者和订阅者之间没有耦合,是不是有优化了一步呢?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();
        }
    }
View Code

分析:ok,这下面试的时候再也不怕面试官问你观察者模式相关知识了吧。。。

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

原文地址:https://www.cnblogs.com/jdzhang/p/7397544.html