设计模式的故事---观察者模式

意图:

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

适用性:

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

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

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

 


 模拟一个《奔跑吧兄弟》中 撕名牌 的游戏
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace DesignerModel.Observer
{
    public abstract class IObserver
    {
        public abstract void Update(string msg);
    }
    /// <summary>
    /// 大广播
    /// </summary>
    public class Subject
    {
        private List<Observer> observers = new List<Observer>();

        public void Attach(Observer o)
        {
            observers.Add(o);
        }
        public void Detach(Observer o)
        {
            observers.Remove(o);
        }

        public void NotifyObservers(string msg)
        {
            foreach (var item in observers)
            {
                item.Update(msg);
            }
        }

    }
    /// <summary>
    /// 游戏者
    /// </summary>
    public class Observer : IObserver
    {
        public Observer(string name)
        {
            Name = name;
        }
        public string Name { get; set; }

        public override void Update(string msg)
        {
            Console.WriteLine(this.Name + "收到信息:" + msg);
        }
        public string CreateNum()
        {
            RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();
            byte[] byteCsp = new byte[10];
            csp.GetBytes(byteCsp);
            return BitConverter.ToString(byteCsp);
        }
    }


    public class GameRule
    {
        private Subject Subject { get; set; }

        public GameRule(Subject subject)
        {
            Subject = subject;
        }
        /// <summary>
        /// 撕名牌
        /// </summary>
        /// <param name="ob1"></param>
        /// <param name="ob2"></param>
        /// <returns></returns>
        public Observer RipTheNameplate(Observer ob1, Observer ob2)
        {
            Random rand1=new Random(1);
            Random rand2=new Random(5);
            int num1 = rand1.Next(1,1000);
            int num2 = rand2.Next(1, 1000);
            if (num1>num2)
            {
                Subject.NotifyObservers(ob1.Name+"Out");
                return ob1;
            }
            else
            {
                Subject.NotifyObservers(ob2.Name + "Out");
                return ob2;
            }
        }
    }


    public static class Client
    {
        public static void Run()
        {
            Subject sub = new Subject();

            Observer Observer1 = new Observer("邓超");
            Observer Observer2 = new Observer("郑凯");
            Observer Observer3 = new Observer("Baby");
            Observer Observer4 = new Observer("包贝尔");
            Observer Observer5 = new Observer("陈赫");
            Observer Observer6 = new Observer("李晨");


            sub.Attach(Observer1);
            sub.Attach(Observer2);
            sub.Attach(Observer3);
            sub.Attach(Observer4);
            sub.Attach(Observer5);
            sub.Attach(Observer6);
            //让邓超和郑凯撕名牌
            new GameRule(sub).RipTheNameplate(Observer1, Observer2);
           
            Console.Read();
        }
    }
}

 

 

 

目标和观察者之间的关系

     按照模式的定义,目标和观察者之间是典型的1对多的关系,但是注意,如果观察者只有一个,也可以是1对1的 关系,这也使得在处理一个对象的状态变化会影响到另一个对象的时候,也可以考虑使用观察者模式。

同样的,一个观察者也可以观察多个目标,如果观察者为多个目标定义的通知更新方法都是Update方法的话,这会带来麻烦,因为需要接受多个目标的通知,如果是一个Update方法,那就需要在方法内部区分,到底这个更新的通知来自于哪一个目标,不同的目标有不同的后续操作。

  一般情况下,观察者应该是不同的观察者目标定义不同的回调方法,这样实现最简单,不需要在update方法内部进行区分。

   单向依赖,在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而目标是不依赖于观察者的。

他们之间的主动权掌握在目标手里,只有目标知道什么时候需要通知观察者,在整个过程中,观察者始终是被动的。被动的等待目标的通知。等待目标传值给它。

对目标而言,所有的观察者都是一样的,目标会一视同仁的对待。当然也可以通过在目标中进行控制,实现有区别的对待观察者。比如某些状态变化了,只需要通知部分观察者。但那是属于稍微变形的用法了。不属于标准的,原始的观察者模式。

 

基本的实现说明:

1,具体的目标实现对象要能维护观察者的注册信息,最简单的实现方案就如同前面的例子那样,采用一个集合来保存观察者的注册信息。

2,具体的目标实现对象需要维护引起通知的状态,一般情况下目标自身的变形使用的情况下,也可以是别的对象的状态。

3,具体的观察者实现对象需要能接受目标的通知,能够接受目标传递的数据,或者是能够主动去获取目标的数据,并进行后续处理

4,如果是一个观察者目标观察多个目标,那么在观察者的更新方法里,需要去判断是来自哪一个目标的通知。一种简单的解决方案就是扩展Update方法,比如在方法里面多传递一个参数进行区分等;还有一个更简单的方法,那就是干脆定义不同的回调方法。

 

命名建议

1,观察者模式又被称为 发布--订阅模式

2,目标接口的定义,建议在名称后面跟Subject

3,观察者接口的定义,建议在名称后面跟observer

观察者接口的更新方法,,建议名称为Update,当然方法的参数可以根据情况而定,参数个数,类型不限。

 

 

触发通知的时机:

  在实现观察者模式的时候,一定要注意触发通知的时机,一般情况下,是在完成了状态维护后触发,因为通知会传递数据,不能够先通知后改数据,这很容易出问题,会导致观察者和目标对象的状态不一致。

比如,目标一触发通知,就有观察者来取值,结果目标还没有更新数据,这就明显地造成了错误。

 

观察者模式的两种模式:

推模式:目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据,相当于是在广播通信

拉模式:目标对象在通知观察者的时候,只传少量信息。如果观察者需要更具体的信息。有观察者主动到目标对象中取,相当于是观察者从目标对象中拉数据。一般这种模式的实现中,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

 

 

推模式是假定目标对象知道观察者需要的数据,而拉模式是目标对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传给观察者,让观察者自己去按需求取值。

推模式可能会使得观察者对象难以复用,因为观察者定义的update方法是按需而定义的,可能无法兼顾没有考虑到的情况。这就意味着出现新情况的时候,就可能需要提供新的update方法,或者干脆重新实现观察者。而拉模式就不会造成这样的情况,因为拉模式下,update方法的参数就是目标对象本身,这基本上是目标对象能传递的最大数据集合了,基本上可以适应各种情况的需求。

 

posted @ 2015-06-30 11:44  尼姑哪里跑  阅读(410)  评论(0编辑  收藏  举报