• 观察者模式(Observer Pattern)


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

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

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

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

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


                                
    代码实现:
     1     public class BankAccount
     2     {
     3         Emailer emailer;  //强信赖关系
     4         Mobile  phoneNumber;    //强信赖关系
     5        
     6         private double _money;
     7 
     8         public Emailer Emailer
     9         {
    10             get { return emailer; }
    11             set { this.emailer = value; }
    12         }
    13         public Mobile PhoneNumber
    14         {
    15             get { return phoneNumber; }
    16             set { this.phoneNumber = value; }
    17         }
    18         public double Money
    19         {
    20             get { return _money; }
    21             set { this._money = value; }
    22         }
    23   
    24         public void WithDraw()
    25         {
    26             emailer.SendEmail(this);
    27             phoneNumber.SendNotification(this);
    28         }
    29 
    30     }

     1     public class Emailer
     2     {
     3        private string _emailer;
     4        public Emailer(string emailer)
     5        {
     6            this._emailer = emailer;
     7        }
     8         public void SendEmail(BankAccount ba)
     9         {
    10             //..
    11             Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, ba.Money);
    12         }
    13     }

     1     public class Mobile
     2     {
     3         private long _phoneNumber;
     4         public Mobile(long phoneNumber)
     5         {
     6             this._phoneNumber = phoneNumber;
     7         }
     8         public void SendNotification(BankAccount ba)
     9         {
    10             Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, ba.Money);
    11         }
    12     }
    此时简单的客户端调用如下:

     1 class Test
     2 {
     3     static void Main(string[] args)
     4     {
     5         BankAccount ba = new BankAccount();
     6         Emailer emailer = new Emailer("abcdwxc@163.com");       
     7         Mobile mobile = new Mobile(13901234567);
     8         ba.Emailer = emailer;
     9         ba.PhoneNumber = mobile;
    10         ba.Money = 2000;       
    11         ba.WithDraw();
    12     }
    13 }
    运行结果如下:

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

     1  public class BankAccount
     2         {
     3             IObserverAccount emailer;        //依赖于接口
     4             IObserverAccount phoneNumber;    //依赖于接口
     5 
     6             private double _money;
     7 
     8             public IObserverAccount Emailer
     9             {
    10                 get { return emailer; }
    11                 set { this.emailer = value; }
    12             }
    13             public IObserverAccount PhoneNumber
    14             {
    15                 get { return phoneNumber; }
    16                 set { this.phoneNumber = value; }
    17             }
    18             public double Money
    19             {
    20                 get { return _money; }
    21                 set { this._money = value; }
    22             }
    23 
    24             public void WithDraw()
    25             {
    26                 emailer.Update(this);
    27                 phoneNumber.Update(this);
    28             }
    29 
    30         }

     1       public class Emailer : IObserverAccount
     2         {
     3             private string _emailer;
     4             public Emailer(string emailer)
     5             {
     6                 this._emailer = emailer;
     7             }
     8             public void Update(BankAccount ba)
     9             {
    10                 //..
    11                 Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, ba.Money);
    12             }
    13         }

     1       public class Mobile : IObserverAccount
     2         {
     3             private long _phoneNumber;
     4             public Mobile(long phoneNumber)
     5             {
     6                 this._phoneNumber = phoneNumber;
     7             }
     8             public void Update(BankAccount ba)
     9             {
    10                 Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, ba.Money);
    11             }
    12         }
    客户端与上方相同,其运行结果也相同。但BankAccount增加和删除通知对象时,还需对其进行修改。对此我们再做如下重构,在BankAccount中维护一个IObserver列表,同时提供相应的维护方法。
     1     public class BankAccount
     2     {
     3         private List<IObserverAccount> Observers = new List<IObserverAccount>();
     4 
     5 
     6         private double _money;
     7 
     8         public double Money
     9         {
    10             get { return _money; }
    11             set { this._money = value; }
    12         }
    13 
    14         public void WithDraw()
    15         {
    16             foreach (IObserverAccount ob in Observers)
    17             {
    18                 ob.Update(this);
    19 
    20             }
    21         }
    22         public void AddObserver(IObserverAccount observer)
    23         {
    24             Observers.Add(observer);
    25         }
    26         public void RemoverObserver(IObserverAccount observer)
    27         {
    28             Observers.Remove(observer);
    29         }
    30 
    31     }
    此时客户端代码如下:
     1   class Test
     2     {
     3         static void Main(string[] args)
     4         {
     5             BankAccount ba = new BankAccount();
     6             IObserverAccount emailer = new Emailer("abcdwxc@163.com");
     7             IObserverAccount mobile = new Mobile(13901234567);
     8 
     9             ba.Money = 2000;
    10             ba.AddObserver(emailer);
    11             ba.AddObserver(mobile);
    12 
    13             ba.WithDraw();
    14         }
    15     }
        走到这一步,已经有了Observer模式的影子了,BankAccount类不再依赖于具体的Emailer或Mobile,而是依赖于抽象的IObserverAccount。存在着的一个问题是Emailer或Mobile仍然依赖于具体的BankAccount,解决这样的问题很简单,只需要再对BankAccount类做一次抽象。如下图:
             
     1  public abstract class Subject
     2     {
     3         private List<IObserverAccount> Observers = new List<IObserverAccount>();
     4 
     5         private double _money;
     6         public Subject(double money)
     7         {
     8             this._money = money;
     9         }
    10 
    11         public double Money
    12         {
    13             get { return _money; }
    14         }
    15      
    16         public void WithDraw()
    17         {
    18             foreach (IObserverAccount ob in Observers)
    19             {
    20                 ob.Update(this);
    21 
    22             }
    23         }
    24         public void AddObserver(IObserverAccount observer)
    25         {
    26             Observers.Add(observer);
    27         }
    28         public void RemoverObserver(IObserverAccount observer)
    29         {
    30             Observers.Remove(observer);
    31         }
    32 
    33     }

    1     public interface IObserverAccount
    2     {
    3         void Update(Subject subject);
    4     }

    1     public class BankAccount : Subject
    2     {
    3         public BankAccount(double money)
    4             : base(money)
    5         { }
    6 
    7     }

     1     public class Emailer : IObserverAccount
     2     {
     3         private string _emalier;      
     4         public Emailer(string emailer )
     5         {
     6             this._emalier = emailer;           
     7         }
     8         public void Update(Subject subject)
     9         {            
    10             Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emalier, subject.Money);
    11         }
    12     }

     1    public class Mobile : IObserverAccount
     2     {
     3         private long _phoneNumber;        
     4         public Mobile(long phoneNumber)
     5         {
     6             this._phoneNumber = phoneNumber;            
     7         }
     8         public void Update(Subject subject)
     9         {
    10             Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, subject.Money);
    11         }
    12     }
    此时客户端实现如下:
     1    class Test
     2     {
     3         static void Main(string[] args)
     4         {
     5             Subject subject = new BankAccount(2000);
     6             subject.AddObserver(new Emailer("abcdwxc@163.com"));
     7             subject.AddObserver(new Mobile(13901234567));
     8 
     9             subject.WithDraw();
    10         }
    11     }

    推模式与拉模式
        对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的 观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的 消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。
    拉模式实现如下:
     1 public abstract class Subject
     2     {
     3         private List<IObserverAccount> Observers = new List<IObserverAccount>();
     4 
     5 
     6         private double _money;
     7 
     8         public double Money
     9         {
    10             get { return _money; }            
    11         }
    12         public Subject(double money)
    13         {
    14             this._money = money;
    15         }
    16         public void WithDraw()
    17         {
    18             foreach (IObserverAccount ob in Observers)
    19             {
    20                 ob.Update();
    21 
    22             }
    23         }
    24         public void AddObserver(IObserverAccount observer)
    25         {
    26             Observers.Add(observer);
    27         }
    28         public void RemoverObserver(IObserverAccount observer)
    29         {
    30             Observers.Remove(observer);
    31         }
    32 
    33     }

    1    public interface IObserverAccount
    2     {
    3         void Update();
    4     }

    1     public class BankAccount :Subject
    2     {
    3         public BankAccount(double money)
    4             : base(money)
    5         { }
    6        
    7     }

     1     public class Emailer : IObserverAccount
     2     {
     3         private string _emalier;
     4         private Subject _subject;
     5         public Emailer(string emailer,Subject subject)
     6         {
     7             this._emalier = emailer;
     8             this._subject = subject;
     9         }
    10         public void Update()
    11         {
    12             //..
    13             Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emalier,_subject.Money);
    14         }
    15     }

     1     public class Mobile : IObserverAccount
     2     {
     3         private long _phoneNumber;
     4         private Subject _subject;
     5         public Mobile(long phoneNumber,Subject subject)
     6         {
     7             this._phoneNumber = phoneNumber;
     8             this._subject = subject;
     9         }
    10         public void Update()
    11         {
    12             Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber,_subject.Money);
    13         }
    14     }
    此时客户端调用如下:
     1    class Test
     2     {
     3         static void Main(string[] args)
     4         {
     5            Subject subject= new BankAccount(2000);          
     6             subject.AddObserver(new Emailer("abcdwxc@163.com",subject));
     7             subject.AddObserver(new Mobile(13901234567,subject));
     8 
     9             subject.WithDraw();
    10         }
    11     }
    .NET中Observer实现:
       
    用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。
     1  public  class Subject
     2     {
     3         public event NotifyEventHandler NotifyEvent;
     4 
     5         private double _money;
     6         public Subject(double money)
     7         {
     8             this._money = money;
     9         }
    10 
    11         public double Money
    12         {
    13             get { return _money; }
    14         }
    15 
    16         public void WithDraw()
    17         {
    18             OnNotifyChange();
    19         }
    20         public void OnNotifyChange()
    21         {
    22             if (NotifyEvent != null)
    23             {
    24                 NotifyEvent(this);
    25             }
    26 
    27         }
    28 
    29     }

     1     public class Emailer
     2     {
     3         private string _emalier;
     4         public Emailer(string emailer)
     5         {
     6             this._emalier = emailer;
     7         }
     8         public void Update(object obj)
     9         {
    10             if (obj is Subject)
    11             {
    12                 Subject subject = (Subject)obj;
    13 
    14                 Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emalier, subject.Money);
    15             }
    16         }
    17 }
    public delegate void NotifyEventHandler(object sender);
    客户端调用如下:
     1     class Test
     2         {
     3             static void Main(string[] args)
     4             {
     5                 Subject subject = new Subject(2000);
     6                 Emailer emailer = new Emailer("abcdwxc@163.com");
     7                 subject.NotifyEvent += new NotifyEventHandler(emailer.Update);
     8            
     9 
    10                 subject.WithDraw();
    11             }
    12         }

    Observer实现要点:

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

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

    3.在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计。
  • 相关阅读:
    面试官:HashMap死循环形成的原因是什么?
    这几个IDEA高级调试技巧,用完就是香
    图示JVM工作原理
    写二进制,姿势一定要骚,省字段,省带宽,提效率...
    阿里大佬总结的40个多线程面试题,你能答上来几个?
    全网最全RabbitMQ总结,别再说你不会RabbitMQ
    .NETCore微服务探寻(三)
    .NETCore微服务探寻(二)
    .NETCore微服务探寻(一)
    谈谈spring-boot-starter-data-redis序列化
  • 原文地址:https://www.cnblogs.com/abcdwxc/p/898856.html
Copyright © 2020-2023  润新知