• 观察者模式


     https://www.cnblogs.com/abcdwxc/archive/2007/09/19/898856.html 本文详细讲述了观察者模式从头开始的起源,到第一次接口解耦,到都是用接口解耦,到推模式只能获取本对象,引出拉模式,到使用.net下的事件最终解耦!

    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

    适用性:

    1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

    2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。

    3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。

    这种模式与事件(event)很像,杰杰借鉴于博主的文章,尝试更加简明的书写观察者模式中的奥秘

    例子仍然使用如下:

    生活中的例子:  

        观 察者定义了对象间一对多的关系,当一个对象的状态变化时,所有依赖它的对象都得到通知并且自动地更新。在ATM取款,当取款成功后,以手机、邮件等方式进行通知。  

      

     代码实现:

      如果只考虑二者之间的关系(取钱的时候通知邮件和电话的话),实现的代码如下:

     #region 银行账户类
        public class BankAccount
        {
            private Email _email;
            private Mobile _mobile;
            private double _money;
            public BankAccount(double money)
            {
                this._money = money;
            }
            public Email Email { get { return _email; } set { _email = value; } }
            public Mobile Mobile { get { return _mobile; } set { _mobile = value; } }
            public double Money { get { return _money; } set { _money = value; } }
            public void WithDraw()
            {
                _email.SendEmail(this);
                _mobile.SendMessage(this);
            }
        }
        #endregion
     #region 通知类
        public class Email
        {
            private string _email;
            public Email(string email)
            {
                this._email = email;
            }
            public void SendEmail(BankAccount bankaccount)
            {
                Console.WriteLine("向{0}发送邮件,取出金额为{1}",_email, bankaccount.Money);
            }
        }
        public class Mobile
        {
            public string _phoneNumer;
            public Mobile(string phoneNumer)
            {
                this._phoneNumer = phoneNumer;
            }
            public void SendMessage(BankAccount bankaccount)
            {
                Console.WriteLine("向{0}打电话通知,取出金额为{1}", _phoneNumer, bankaccount.Money);
            }
        }
        #endregion
     static void Main(string[] args)
            {
                BankAccount bankaccount = new BankAccount(30000d);
                Email email = new Email("12345@qq.com");
                Mobile mobile = new Mobile("0411-12345678");
                bankaccount.Email = email;
                bankaccount.Mobile = mobile;
                bankaccount.WithDraw();
                Console.ReadKey();
            }

    =====================***********************=====================***********************=====================

    显然这种方式书写的代码考虑的太狭窄,此时通知与取钱账户之间形成了紧耦合,也就是说,无法扩展取钱去通知其他账户,除非修改账户类本身。

     显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的BankAccount类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消BankAccount和具体的通知对象之间依赖。
    由此我们由左图转换到右图。

    我只需要为所有通知类增加一个公共接口,让所有的通知类实现这个接口即可

      #region 通知接口
        public interface IObserverAccount
        {
            void Update(BankAccount bankaccount);
        }
        #endregion
      #region 通知类
        public class Email: IObserverAccount
        {
            private string _email;
            public Email(string email)
            {
                this._email = email;
            }
            public void Update(BankAccount bankaccount)
            {
                Console.WriteLine("向{0}发送邮件,取出金额为{1}",_email, bankaccount.Money);
            }
        }
        public class Mobile: IObserverAccount
        {
            public string _phoneNumer;
            public Mobile(string phoneNumer)
            {
                this._phoneNumer = phoneNumer;
            }
            public void Update(BankAccount bankaccount)
            {
                Console.WriteLine("向{0}打电话通知,取出金额为{1}", _phoneNumer, bankaccount.Money);
            }
        }
        #endregion

    对于银行账户类只需要将具体的实现类,换成接口即可,实现面向接口的编程

        #region 银行账户类
        public class BankAccount
        {
            private IObserverAccount _email;
            private IObserverAccount _mobile;
            private double _money;
            public BankAccount(double money)
            {
                this._money = money;
            }
            public IObserverAccount Email { get { return _email; } set { _email = value; } }
            public IObserverAccount Mobile { get { return _mobile; } set { _mobile = value; } }
            public double Money { get { return _money; } set { _money = value; } }
            public void WithDraw()
            {
                _email.Update(this);
                _mobile.Update(this);
            }
        }
        #endregion

    对于实现与上方一致,将具体的类转换为接口就可以(转不转都行,因为传递的时候,就算不转,程序也会帮我们隐示转换)

     static void Main(string[] args)
            {
                BankAccount bankaccount = new BankAccount(30000d);
                IObserverAccount email = new Email("12345@qq.com");
                IObserverAccount mobile = new Mobile("0411-12345678");
                bankaccount.Email = email;
                bankaccount.Mobile = mobile;
                bankaccount.WithDraw();
                Console.ReadKey();
            }

    结果也是相同的。

    =====================***********************=====================***********************=====================

    现在出现的问题是账户类中固定了只能选两种通知方式,无法手动增减,因此考虑,将账户中的通知方式变为接口列表

    此时账户类修改为:

     #region 银行账户类
        public class BankAccount
        {
            private List<IObserverAccount> list = new List<IObserverAccount>();
            private double _money;
            public BankAccount(double money)
            {
                this._money = money;
            }
            public void AddObserver(IObserverAccount iobserveraccount)
            {
                list.Add(iobserveraccount);
            }
            public void RemoveObserver(IObserverAccount iobserveraccount)
            {
                list.Remove(iobserveraccount);
            }
            public double Money { get { return _money; } set { _money = value; } }
            public void WithDraw()
            {
                foreach (IObserverAccount item in list)
                {
                    item.Update(this);
                }
            }
        }
        #endregion

    实现方式微调如下:

     static void Main(string[] args)
            {
                BankAccount bankaccount = new BankAccount(30000d);
                IObserverAccount email = new Email("12345@qq.com");
                IObserverAccount mobile = new Mobile("0411-12345678");
                bankaccount.AddObserver(email);
                bankaccount.AddObserver(mobile);
                //bankaccount.Email = email;
                //bankaccount.Mobile = mobile;
                bankaccount.WithDraw();
                Console.ReadKey();
            }

    =====================***********************=====================***********************=====================

     走到这一步,已经有了Observer模式的影子了,BankAccount类不再依赖于具体的Emailer或Mobile,而是依赖于抽象的IObserverAccount。存在着的一个问题是Emailer或Mobile仍然依赖于具体的BankAccount,解决这样的问题很简单,只需要再对BankAccount类做一次抽象。如下图:

     我们设置一个账户的抽象基类(所有具体的账户类都要实现这个接口):

     #region 银行账户抽象基类
        public abstract class Subject
        {
            private List<IObserverAccount> list = new List<IObserverAccount>();
            private double _money;
            public Subject(double money)
            {
                this._money = money;
            }
            public void AddObserver(IObserverAccount iobserveraccount)
            {
                list.Add(iobserveraccount);
            }
            public void RemoveObserver(IObserverAccount iobserveraccount)
            {
                list.Remove(iobserveraccount);
            }
            public double Money { get { return _money; } set { _money = value; } }
            public void WithDraw()
            {
                foreach (IObserverAccount item in list)
                {
                    item.Update(this);
                }
            }
        }
        #endregion
     #region 银行账户类
        public class BankAccount : Subject
        {
            public BankAccount(double money) : base(money)
            {
            }
        }
        #endregion

    将通知类实现具体账户的参数,全部修改为该接口参数,实现面向接口编程

      #region 通知接口
        public interface IObserverAccount
        {
            void Update(Subject subject);
        }
        #endregion
        #region 通知类
        public class Email: IObserverAccount
        {
            private string _email;
            public Email(string email)
            {
                this._email = email;
            }
            public void Update(Subject subject)
            {
                Console.WriteLine("向{0}发送邮件,取出金额为{1}",_email, subject.Money);
            }
        }
        public class Mobile: IObserverAccount
        {
            public string _phoneNumer;
            public Mobile(string phoneNumer)
            {
                this._phoneNumer = phoneNumer;
            }
            public void Update(Subject subject)
            {
                Console.WriteLine("向{0}打电话通知,取出金额为{1}", _phoneNumer, subject.Money);
            }
        }
        #endregion

    具体实现代码仍然不变

      static void Main(string[] args)
            {
                BankAccount bankaccount = new BankAccount(30000d);
                IObserverAccount email = new Email("12345@qq.com");
                IObserverAccount mobile = new Mobile("0411-12345678");
                bankaccount.AddObserver(email);
                bankaccount.AddObserver(mobile);
                //bankaccount.Email = email;
                //bankaccount.Mobile = mobile;
                bankaccount.WithDraw();
                Console.ReadKey();
            }

    =====================***********************=====================***********************=====================

    推模式与拉模式
        对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的 观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的 消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。

    (简要就是推模式的通知者无法选择需要接收对象的信息(也无法控制什么时候接收),而拉模式,可以选择去接收哪个对象的信息,以及接收哪些信息,而不是接收全部信息)
    拉模式实现如下:

     银行账户方不变化,将通知类方,从接口开始去除指定的银行账户基类,将账户基类由各个具体的通知方,在构造函数时指定

    //该块不变
     #region 银行账户抽象基类
        public abstract class Subject
        {
            private List<IObserverAccount> list = new List<IObserverAccount>();
            private double _money;
            public Subject(double money)
            {
                this._money = money;
            }
            public void AddObserver(IObserverAccount iobserveraccount)
            {
                list.Add(iobserveraccount);
            }
            public void RemoveObserver(IObserverAccount iobserveraccount)
            {
                list.Remove(iobserveraccount);
            }
            public double Money { get { return _money; } set { _money = value; } }
            public void WithDraw()
            {
                foreach (IObserverAccount item in list)
                {
                    item.Update();
                }
            }
        }
        #endregion
        #region 银行账户类
        public class BankAccount : Subject
        {
            public BankAccount(double money) : base(money)
            {
            }
        }
      #region 通知接口
        public interface IObserverAccount
        {
            void Update();
        }
        #endregion
        #region 通知类
        public class Email: IObserverAccount
        {
            private string _email;
            private Subject _subject;
            public Email(string email, Subject subject)
            {
                this._email = email;
                this._subject = subject;
            }
            public void Update()
            {
                Console.WriteLine("向{0}发送邮件,取出金额为{1}",_email, _subject.Money);
            }
        }
        public class Mobile: IObserverAccount
        {
            public string _phoneNumer;
            private Subject _subject;
            public Mobile(string phoneNumer,Subject subject)
            {
                this._phoneNumer = phoneNumer;
                this._subject = subject;
            }
            public void Update()
            {
                Console.WriteLine("向{0}打电话通知,取出金额为{1}", _phoneNumer, _subject.Money);
            }
        }
        #endregion
    static void Main(string[] args)
            {
                Subject bankaccount = new BankAccount(30000d);
                Subject bankaccount2 = new BankAccount(20000d);
                IObserverAccount email = new Email("12345@qq.com", bankaccount);
                IObserverAccount mobile = new Mobile("0411-12345678", bankaccount2);
                bankaccount.AddObserver(email);
                bankaccount.AddObserver(mobile);
                bankaccount.WithDraw();
                Console.ReadKey();
            }

     =====================***********************=====================***********************=====================

    正如最上方提到的在C#中有事件这个概念,它本身就是一种观察者模式的实现方式,借助于它,我们可以构建出更加完善的观察者模式!

    在银行账户类中添加一个事件,事件可以动态绑定,这样也不需要list集合了

     #region 银行账户抽象基类
        public abstract class Subject
        {
            public event Action<object> NotifyEvent;
            private double _money;
            public Subject(double money)
            {
                this._money = money;
            }
            public double Money { get { return _money; } set { _money = value; } }
            public void WithDraw()
            {
                if (NotifyEvent != null)
                {
                    NotifyEvent(this);
                }
            }
        }
        #endregion
       #region 银行账户类
        public class BankAccount : Subject
        {
            public BankAccount(double money) : base(money)
            {
            }
        }

    我将通知接口写成具有泛型的形式,这样以后在账户的具体类中添加属性等,我仍然可以访问的到

    #region 通知接口
        public interface IObserverAccount
        {
            void Update<T>(object obj) where T : Subject;
        }
     #region 通知类
        public class Email : IObserverAccount
        {
            private string _email;
            public Email(string email)
            {
                this._email = email;
            }
            public void Update<T>(object obj) where T: Subject
            {
                try
                {
                    T t = (T)obj;
                    Console.WriteLine("向{0}发送邮件,取出金额为{1}", _email, t.Money);
                }
                catch
                {
                }  
            }
        }
     static void Main(string[] args)
            {
                Subject bankaccount = new BankAccount(30000d);
                IObserverAccount email = new Email("12345@qq.com");
                bankaccount.NotifyEvent += email.Update<BankAccount>;
                bankaccount.WithDraw();
                Console.ReadKey();
            }

    Observer实现要点:

    1.使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。

    2.目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。

    3.在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计

    虽然我们使用event实现的属于推模式,但是这种模式更加常用,基本上需求都可以满足 ,最终的需求也都可以实现了。

     =====================***********************=====================***********************=====================

    杰杰继续思索,如果将通知类的接口,扩展成通知的抽象基类的话,在其中加入list,记录所有的通知类对象,统一绑定到指定的账户类的事件上,岂不是一次就可以完成

    //银行账户类保持不变
     #region 银行账户抽象基类
        public abstract class Subject
        {
            public event Action<object> NotifyEvent;
            private double _money;
            public Subject(double money)
            {
                this._money = money;
            }
            public double Money { get { return _money; } set { _money = value; } }
            public void WithDraw()
            {
                if (NotifyEvent != null)
                {
                    NotifyEvent(this);
                }
            }
        }
        #endregion
        #region 银行账户类
        public class BankAccount : Subject
        {
            public BankAccount(double money) : base(money)
            {
            }
        }
        #endregion
     #region 通知基类
        public abstract class ObserverAccountClass
        {
            public ObserverAccountClass()
            {
                list.Add(this);//将自己绑定到list中
            }
            public abstract void Update<T>(object obj) where T : Subject;
            List<ObserverAccountClass> list = new List<ObserverAccountClass>();
            public void Add(ObserverAccountClass observeraccountclass)
            {
                list.Add(observeraccountclass);
            }
            public void Remove(ObserverAccountClass observeraccountclass)
            {
                list.Remove(observeraccountclass);
            }
            public void BindingEvents(Subject subject)
            {
                foreach (ObserverAccountClass item in list)
                {
                    subject.NotifyEvent += item.Update<Subject>;
                }
            }
        }
        #endregion

    将具体通知类继承它

       #region 通知类
        public class Email : ObserverAccountClass
        {
            private string _email;
            public Email(string email)
            {
                this._email = email;
            }
    
            public override void Update<T>(object obj)
            {
                try
                {
                    T t = (T)obj;
                    Console.WriteLine("向{0}发送邮件,取出金额为{1}", _email, t.Money);
                }
                catch { }
            }
        }
    static void Main(string[] args)
            {
                Subject bankaccount = new BankAccount(30000d);
                ObserverAccountClass email = new Email("12345@qq.com");
                ObserverAccountClass email2 = new Email("22335@qq.com");
                email.Add(email2);
                email.BindingEvents(bankaccount);
                //bankaccount.NotifyEvent += email.Update<BankAccount>;
                bankaccount.WithDraw();
                Console.ReadKey();
            }

     (写的行数其实与不使用list是一样的,只不过可以通过另外一种形式一起绑定)

    其实可扩展的东西有很多,自己慢慢发掘,体会观察者模式的好处吧!

    最后强调一点,其实讲了那么多的过程,最最基本,也是最最重要的就是event,个人理解这是C#自带的观察者模式,十分强大!

  • 相关阅读:
    Java基础课程---将一个字符串反转,将字符串中指定部分进行反转,比如,"abcdefg", 反转为"abfedcg"
    在Centon64位中卸载安装时系统自带的--openjdk,并且安装自己需要的jdk
    Java基础课程---sleep()方法 和 wait()方法的异同(面试题)
    Java基础课程---权限修饰符
    Java基础课程---
    MySQL 创建库
    brew
    android webview load 本地文件需要注意的地方
    android下隐藏标题栏
    android webview无法加载网页
  • 原文地址:https://www.cnblogs.com/ningxinjie/p/12191511.html
Copyright © 2020-2023  润新知