设计模式-观察者模式


定义

观察者模式(有时又被称为发布-订阅Subscribe>模式、模型-视图View>模式、源-收听者Listener>模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。 

基本简介

观察者模式(Observer)完美的将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
 

(1)抽象主题(Subject)

 持有一个观察者对象的集合,提供增加,删除观察者对象的接口,当需要关注的状态变化时候,需要通知所有持有的观察者对象。

(2)具体主题(Concrete Subject

 被观察者的具体实现...

(3)抽象观察者(Observer

 定义一个接口,在接受通知时候更新状态

(4)具体观察者Concrete Observer

 观察者的具体实现...

 

UML图

 

示例

 (1.1)抽象主题类Subject

    /// <summary>
    /// 抽象主题
    /// </summary>
    public abstract class Subject
    {
        /// <summary>
        /// 所有观察者对象
        /// </summary>
        private List<Observer> observers = new List<Observer>();

        /// <summary>
        /// 增加观察者对象
        /// </summary>
        /// <param name="observer"></param>
        public void AddObserver(Observer observer)
        {
            observers.Add(observer);
        }

        /// <summary>
        /// 移除观察者对象
        /// </summary>
        /// <param name="observer"></param>
        public void RemoveObserver(Observer observer)
        {
            observers.Remove(observer);
        }

        /// <summary>
        /// 发送通知
        /// </summary>
        public void Notify()
        {
            foreach (var ob in observers)
            {
                ob.Update();
            }
        }
    }

(1.2)具体主题类ConcreteSubject

    /// <summary>
    /// 具体通知者
    /// </summary>
    public class ConcreteSubject : Subject
    {
        /// <summary>
        /// 具体观察者状态
        /// </summary>
        public string SubjectState { get; set; }
    }

(1.3)抽象观察者类

   /// <summary>
    /// 观察者类
    /// </summary>
    public abstract class Observer
    {
        public abstract void Update();
    }

(1.4)具体观察者类

    /// <summary>
    /// 具体观察者
    /// </summary>
    public class ConcreteObserver : Observer
    {
        public string observerState { get; set; }
        public string Name { get; set; }
        public ConcreteSubject subjcSubject { get; set; }

        public ConcreteObserver(ConcreteSubject subject, string name)
        {
            this.Name = name;
            this.subjcSubject = subject;
        }

        public override void Update()
        {
            observerState = subjcSubject.SubjectState;
            System.Console.WriteLine("the observer's state of {0} is {1}", Name, observerState);
        }
    }

(1.5)控制台调用

    class Program
    {
        static void Main(string[] args)
        {
            ConcreteSubject subject = new ConcreteSubject();

            subject.AddObserver(new ConcreteObserver(subject, "ObA"));
            subject.AddObserver(new ConcreteObserver(subject, "ObB"));
            subject.AddObserver(new ConcreteObserver(subject, "ObC"));

            subject.SubjectState = "Ready";
            subject.Notify();

            System.Console.ReadKey();
        }
    }

(1.6)运行结果

 

优缺点

优点:
1、 Subject和Observer之间是松耦合的,分别可以各自独立改变。
2、 Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。
3、 遵守大部分GRASP原则和常用设计原则,高内聚、低耦合。
缺陷:
1、  依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。
2、 如果一个Subject被大量Observer订阅的话,在广播通知的时候可能会有效率问题。
 
思考:抽象已经降低Subject和Observer之间的耦合度,但是他们之间依旧存在依赖。而我们观察者模式一定程度上注重着对观察者的通知,也就是动作的传递,目前Subject中依靠维持抽象具体观察对象,在发送通知时候循环每个具体观察者,调用通知方法,那么最终我们的目的就是主题能调用相应的通知方法。换个角度说我们是不是可以维护一个方法列表,那么C#中的委托(多播委托)似乎可以实现我们的这一目标。

 

C#委托改版

我们以一个主题通知发送消息的例子来演示

(1.1)抽象主题

    /// <summary>
    /// 抽象主题
    /// </summary>
    public interface ISubject
    {
        void Notify();
    }

(1.2)声明委托和具体主题

    /// <summary>
    /// 声明委托
    /// </summary>
    public delegate void MsgEvent();
    /// <summary>
    /// 具体主题
    /// </summary>
    public class ConcreteSubject : ISubject
    {

        /// <summary>
        /// 定义委托事件
        /// </summary>
        public event MsgEvent MsgAction;

        /// <summary>
        /// 执行通知
        /// </summary>
        public void Notify()
        {
            if (MsgAction != null)
                MsgAction();
        }
    }

(1.3)添加具体观察者 站内信,邮件,短信

    /// <summary>
    /// 站内信
    /// </summary>
    public class InsideLetterMsg
    {
        /// <summary>
        /// 发送站内信
        /// </summary>
        public void SendInsideLetterMsg()
        {
            Console.WriteLine("发送站内信.....");
        }
    }

    /// <summary>
    /// 邮件
    /// </summary>
    public class MailMsg
    {
        /// <summary>
        /// 发送邮件
        /// </summary>
        public void SendMailMsg()
        {
            Console.WriteLine("发送邮件.....");
        }
    }

    /// <summary>
    /// 短信
    /// </summary>
    public class SMSMsg
    {
        /// <summary>
        /// 发送短信
        /// </summary>
        public void SendSMSMsg()
        {
            Console.WriteLine("发送短信.....");
        }
    }

(1.4)客户端调用

    class Program
    {
        static void Main(string[] args)
        {
            ConcreteSubject subject = new ConcreteSubject();

            //注册事件
            subject.MsgAction += (new InsideLetterMsg()).SendInsideLetterMsg;  //站内信
            subject.MsgAction += (new MailMsg()).SendMailMsg; //邮件
            subject.MsgAction += (new SMSMsg()).SendSMSMsg;   //短信

            //开始发送消息了
            subject.Notify();   

            Console.ReadKey();

        }
    }

(1.5)结果

 

这个例子我们更多关注的是行为,主题对于观察者行为的执行和通知。我们只需要在调用的时候将观察者的方法注册到主题中即可。省去了主题需要维护观察者对象,循环调用观察者对象方法的过程。顺带我们也看一下我们创建的委托具体是what?

 

委托看一看

(1.1)看图说话

(1.2)System.MulticastDelegate

  

   _invocationList通常这个字段为null,当我们构造一个委托链是,他可以引用一个委托数组,也就是说我们给委托+=方法时候,实际是操作它.

   我们可以推断出当我们调用委托方法时候,代码大致是这样的(下面代码不是可运行的代码,只是预估大概的逻辑,仅供观看),不知道在观察者这块顺带写了下委托是不是有点

   跑偏,委托这里只是顺带提一下,更多的知识面肯定没涉及到,只是帮助大家理解下.

            //从委托链中获取
            Delegate[] deleagetset = _invocationList as Delegate[];
            if (deleagetset != null)
            {
                foreach (MsgAction m in deleagetset)
                {
                    m(value); //调用每个委托
                }
            }
            else 
            {
                //当前不是委托链 直接 invoke
                _methodPtr.Invoke(_target, value);
            }

 上边的例子只是单纯的循环,中途有一个调用委托失败都没有健壮的处理,所以MulticastDelegate类提供了另一个实例方法GetInvocationList,用于显示调用链中的每一个委托,具体大家可以查阅相关资料。That's all!

 

文章代码链接http://files.cnblogs.com/files/mongo/BlogObserver.zip

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2020 CODEPRJ.COM