观察者模式

 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#自带的观察者模式,十分强大!

原文地址:https://www.cnblogs.com/ningxinjie/p/12191511.html