zoukankan      html  css  js  c++  java
  • 16.观察者模式(Observer Pattern)

    动机(Motivate):
        在软件构建 过程中,我们需要为某些对象建立一种“通知依赖关系” --------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面 向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
    意图(Intent):
       
    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
                                                                             -------《设计模式》GOF
    结构图(Struct):
                
    适用性:

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

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

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

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

                                
    代码实现:

     
       class BankAccount
        {
            private Emailer emailer;        //强信赖关系
    
    
            private Mobile phoneNumber;     //强信赖关系
    
    
            private double _money;
    
    
            public Emailer Emailer
            {
                get { return emailer; }
                set { emailer = value; }
            }
    
            public Mobile PhoneNumber
            {
                get { return phoneNumber; }
                set { phoneNumber = value; }
            }
    
    
            public double Money
            {
                get { return _money; }
                set { _money = value; }
            }
    
    
    
    
            public void WithDraw()
            {
                emailer.SendEmail(this);
                phoneNumber.SenderNotification(this);
    
            }
        }
        class Emailer
        {
            private string _emailer;
    
            public Emailer(string emailer)
            {
                this._emailer = emailer;
            }
    
            public void SendEmail(BankAccount ba)
            {
                //..
                Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, ba.Money);
            }
        }
        class Mobile
        {
            private long _phoneNumber;
    
            public Mobile(long phoneNumber)
            {
                this._phoneNumber = phoneNumber;
            }
    
            public void SenderNotification(BankAccount ba)
            {
                //..
                Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, ba.Money);
            }
        }
    客户端调用如下:
       static void Main(string[] args)
            {
    
                ////------------------------------  模式1  ----------------------------------------
    
                //model1.BankAccount ba = new 模式1.BankAccount();
    
                //model1.Emailer emailer = new model1.Emailer("abcdwxc@163.com");
                //model1.Mobile mobile = new model1.Mobile(13901234567);
    
                //ba.Emailer = emailer;
                //ba.PhoneNumber = mobile;
    
                //ba.Money = 2000;
    
                //ba.WithDraw();
    
                ////-------------------------------------------------------------------------------
           }

    运行结果如下:

        由此可见程序可以正常运行,但请注意BandAccount和Emailer及Mobile之间形成了一种双向的依赖关系,即BankAccount调用了Emailer及Mobile的方法,而Emailer及Mobile调用了BnadAccount类的属性。如果有其中一个类变化,有可能会引起另一个的变化。如果又需添加一种新的通知方式,就得在BankAccount的WithDraw()方法中增加对该中通知方式的调用。
        显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的BankAccount类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消BankAccount和具体的通知对象之间依赖。
    由此我们由左图转换到右图。
                
    实例代码如下:

        interface IObserverAccount
        {
            void Update(BankAccount ba);
        }
     
        class BankAccount
        {
            private List<IObserverAccount> obServers = new List<IObserverAccount>();
    
            private double _money;
    
            public double Money
            {
                get { return _money; }
                set { _money = value; }
            }
    
            public void WithDraw()
            {
                foreach (IObserverAccount ob in obServers)
                {
                    ob.Update(this);
                }
            }
    
    
            public void AddObServer(IObserverAccount ob)
            {
                this.obServers.Add(ob);
            }
    
            public void RemoveObServer(IObserverAccount ob)
            {
                this.obServers.Remove(ob);
            }
        }
     
        class Emailer : Pattern_P16_行为型模式_观察者模式.模式2.IObserverAccount
        {
            private string _emailer;
    
            public Emailer(string emailer)
            {
                this._emailer = emailer;
            }
    
    
            public void Update(BankAccount ba)
            {
                Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, ba.Money);
            }
        }
        class Mobile : IObserverAccount
        {
            private long _phoneNumber;
    
    
            public Mobile(long phoneNumber)
            {
                this._phoneNumber = phoneNumber;
            }
    
            public void Update(BankAccount ba)
            {
                Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, ba.Money);
            }
        }

    客户端与上方相同,其运行结果也相同。但BankAccount增加和删除通知对象时,还需对其进行修改。对此我们再做如下重构,在BankAccount中维护一个IObserver列表,同时提供相应的维护方法。

        class BankAccount
        {
            private List<IObserverAccount> obServers = new List<IObserverAccount>();
    
            private double _money;
    
            public double Money
            {
                get { return _money; }
                set { _money = value; }
            }
    
            public void WithDraw()
            {
                foreach (IObserverAccount ob in obServers)
                {
                    ob.Update(this);
                }
            }
    
    
            public void AddObServer(IObserverAccount ob)
            {
                this.obServers.Add(ob);
            }
    
            public void RemoveObServer(IObserverAccount ob)
            {
                this.obServers.Remove(ob);
            }
        }

    此时客户端代码如下:

                ////------------------------------  模式2  ----------------------------------------
    
                //model2.BankAccount ba = new model2.BankAccount();
    
                //ba.Money = 2000;
    
                //model2.IObserverAccount emailer = new model2.Emailer("abcdwxc@163.com");
                //model2.IObserverAccount phone = new model2.Mobile(13901234567);
    
                //ba.AddObServer(emailer);
                //ba.AddObServer(phone);
    
                //ba.WithDraw();
    
                ////-------------------------------------------------------------------------------

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

     
     
       internal abstract class Subject
        {
            private List<IObserverAccount> lObservers = new List<IObserverAccount>();
    
            private double _money;
    
            public double Money
            {
                get { return _money; }
                set { _money = value; }
            }
    
            public Subject(double money)
            {
                this.Money = money;
            }
    
            public void WithDraw()
            {
                foreach (IObserverAccount ob in lObservers)
                {
                    ob.Update(this);
                }
            }
    
            public void AddObserverAccount(IObserverAccount ob)
            {
                lObservers.Add(ob);
            }
    
            public void RemoveObserverAccount(IObserverAccount ob)
            {
                lObservers.Remove(ob);
            }
        }
     
        internal interface IObserverAccount
        {
            void Update(Subject subject);
        }
       internal class BankAccount:Subject
        {
           public BankAccount(double money)
               : base(money)
           { 
           
           }
    
        }
        internal class Emailer : Pattern_P16_行为型模式_观察者模式.模式3.IObserverAccount
        {
            private string _emailer;
    
            public Emailer(string emailer)
            {
                this._emailer = emailer;
            }
    
            public void Update(Subject subject)
            {
                Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _emailer, subject.Money);
            }
        }
       internal class Mobile : IObserverAccount
        {
            private long _phoneNumber;
    
            public Mobile(long phoneNumber)
            {
                this._phoneNumber = phoneNumber;
            }
    
    
            public void Update(Subject subject)
            {
                Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, subject.Money);
            }
        }

    此时客户端实现如下:

                ////------------------------------  模式3  ----------------------------------------
    
                //model3.Subject subject = new model3.BankAccount(2000);
    
                //model3.IObserverAccount emailer = new model3.Emailer("abcdwxc@163.com");
                //model3.IObserverAccount mobile = new model3.Mobile(13901234567);
    
                //subject.AddObserverAccount(emailer);
                //subject.AddObserverAccount(mobile);
    
                //subject.WithDraw();
                ////-------------------------------------------------------------------------------


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

       internal abstract class Subject
        {
            private List<IObserverAccount> lObservers = new List<IObserverAccount>();
    
            private double _money;
    
            public double Money
            {
                get { return _money; }
                set { _money = value; }
            }
    
    
            public Subject(double money)
            {
                this._money = money;
            }
    
            public void WithDraw()
            {
                foreach (IObserverAccount ob in lObservers)
                {
                    ob.Update();
                }
            }
    
    
            public void AddObserver(IObserverAccount ob)
            {
                lObservers.Add(ob);
            }
    
            public void RemoveObserver(IObserverAccount ob)
            {
                lObservers.Remove(ob);
            }
    
        }
        internal interface IObserverAccount
        {
            void Update();
        }
        class BankAccount:Subject
        {
            public BankAccount(double money)
                : base(money)
            { 
            }
        }
     
        internal class Emailer : Pattern_P16_行为型模式_观察者模式.模式4.IObserverAccount
        {
            private string _emailer;
            private Subject _subject;
    
            public Emailer(string emailer, Subject subject)
            {
                this._emailer = emailer;
                this._subject = subject;
            }
    
            public void Update()
            {
                //..
                Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, _subject.Money);
            }
    
        }
        class Mobile : IObserverAccount
        {
            private long _phoneNumber;
            public Subject _subject;
    
            public Mobile(long phoneNumber, Subject subject)
            {
                this._phoneNumber = phoneNumber;
                this._subject = subject;
            }
    
    
            public void Update()
            {
                Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _phoneNumber, _subject.Money);
            }
        }

    此时客户端调用如下:

     
                ////------------------------------  模式4  ----------------------------------------
    
                //model4.Subject subject = new model4.BankAccount(2000);
    
                //model4.IObserverAccount emailer = new model4.Emailer("abcdwxc@163.com", subject);
                //model4.IObserverAccount mobile = new model4.Mobile(13901234567, subject);
    
                //subject.AddObserver(emailer);
                //subject.AddObserver(mobile);
    
                //subject.WithDraw();
    
                ////-------------------------------------------------------------------------------

    .NET中Observer实现:
       
    用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。

     
       internal class Subject
        {
           public event DelegateClass.NotifyEventHander notifyEvent;
    
           private double _money;
    
           public double Money
           {
               get { return _money; }
               set { _money = value; }
           }
    
           public Subject(double money)
           {
               this._money = money;
           }
    
    
           public void WithDraw()
           {
               OnNotifyChange();
           }
    
           public void OnNotifyChange()
           {
               if (notifyEvent != null)
               {
                   notifyEvent(this);
               }
    
           }
        }
        class Emailer
        {
            private string _emailer;
    
            public Emailer(string emailer)
            {
                this._emailer = emailer;
            }
    
            public void Update(object obj)
            {
                if (obj is Subject)
                {
                    Subject subject = (Subject)obj;
    
                    Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, subject.Money);
                }
            }
    
        }
    public delegate void NotifyEventHandler(object sender);
    客户端调用如

    客户端调用如下:

      //------------------------------  模式5  ----------------------------------------
    
                model5.Subject subject = new model5.Subject(2000);
    
                model5.Emailer emailer = new model5.Emailer("abcdwxc@163.com");
                model5.Mobile mobile = new model5.Mobile(13901234567);
    
                subject.notifyEvent += new model5.DelegateClass.NotifyEventHander(emailer.Update);
                subject.notifyEvent += new model5.DelegateClass.NotifyEventHander(mobile.Update);
    
                subject.WithDraw();
    
                //-------------------------------------------------------------------------------

     Observer实现要点:

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

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

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

  • 相关阅读:
    pixijs shader 制作百叶窗效果
    pixijs shader 贴图溶解效果教程
    shadertoy使用教程
    pixijs shader教程
    glsl shader简明教程系列1
    javascript判断mp3是否播放完
    wxWidgets Tutorial
    NYOJ 214 最长上升子序列nlogn
    解决codeforces访问慢的问题
    dp 斯特林数 HDU2512一卡通大冒险
  • 原文地址:https://www.cnblogs.com/1285026182YUAN/p/5167022.html
Copyright © 2011-2022 走看看