Observer 与 Subject 互为耦合,但是这种耦合的双方都依赖于抽象,而不依赖于具体。

一、观察者模式

目的

我们都知道解决一个问题有N种解决方式,但在面向对象的设计中如何能做到“高内聚,低耦合”,设计可重用的对象才是我们追求的。在设计过程中,我们经常会接触到一种情况:一个对象的行为引发其它多个对象相应的行为。这时我们便可以通过观察者模式的设计思想来设计对象模型。

概述

观察者模式(Observer Pattern)是设计模式中行为模式的一种,它解决了上述具有一对多依赖关系的对象的重用问题。此模式的参与者分为两大类,一类是被观察的目标,另一类是观察该目标的观察者们。正因为该模式是基于“一对多”的关系,所以该模式一般是应用于由一个目标对象和N个观察者对象组成(当然也可以扩展为有多个目标对象,但我们现在只讨论前者)的场合。当目标对象的状态发生改变或做出某种行为时,正在观察该目标对象的观察者们将自动地、连锁地作出相应的响应行为。

原理

我们可以把观察目标理解为主动方、发布方、主体等;把观察者理解为被动方、订阅方、观察器等。目标是整个行为链的源头,其它观察者都依赖于它的变化而作出响应。为了实现低耦合,我们不能使用“直接调用”的方式而需要利用“订阅(清单)-通知”的机制去完成设计。通俗地说就是观察者向目标“订阅”它的改变,而目标发生改变后就“通知”所有已经“订阅”了它的改变的观察者,从而执行“订阅”的内容。这种机制的好处在于降低耦合度,分工明确,目标只负责在自身状态发生改变或做出某种行为时向自身的订阅清单发出“通知”,而不是直接调用观察者的行为(方法);观察者只负责向目标“订阅”它的变化,以及定义自身在收到目标“通知”后所需要做出的具体行为(也就是订阅的内容)。就像我们向出版社订阅报刊一样,出版社有新一期报刊发行时并不是直接跟每位订阅者联系,而是“通知”订阅者名单按顺序给每位订阅者发送所订报刊。

二、C#中的观察者模式

概述

每种编程架构及程序语言,对观察者模式都有不通的具体实现。在.NET框架中,C#语言使用委托以及事件,可以很好的实现观察者模式。委托相当于“订阅清单”的角色,当目标中关联了该委托的事件被触发时,则委托将自动按序执行观察者注册于委托中的方法。

模型与观察者基类

我们把观察者模式的参与者都描述为派生自模型及观察者二个抽象基类的类。模型规划了事件,而观察者则规划了订阅及行为。

模型需要做的只是声明委托以及声明委托类型的事件。当然,还可以附加上封装了触发委托事件的方法。所有派生自模型的类都是具体目标,它们所要做的只是在适当的场合触发事件。(即发出“通知”)。

在观察者基类中,我们通过构造器将抽象的响应方法注册(订阅)于委托事件中。所有派生自观察者基类的类都是具体观察者。因为订阅行为已经在抽象基类完成,具体观察者需要做的只是通过覆盖观察者基类的方法去定义具体需要响应的行为,和通过构造器把需要观察的具体目标传递给基类构造器。

优点

通过对模型与观察者基类的分析可知,委托与事件的机制几乎消除了这两个模块之间的耦合,灵活性提高了很多。如果需要增加观察者,则只需要覆盖基类抽象方法及把观察目标传递给基类。

三、事例

题目:猫大叫,两只老鼠开始逃跑,主人醒来,宝宝也醒来了并且哭了起来。

解决方案:

  1. 建立模型(目标基类)

    namespace DelegateEvent
    {
    /// <summary>
    /// 在Observer Pattern(观察者模式)中,此类作为所有Subject(目标)的抽象基类
    /// 所有要充当Subject的类(在此事例中为"猫")都继承于此类.
    /// 我们说此类作为模型,用于规划目标(即发布方)所产生的事件,及提供触发
    /// 事件的方法.
    /// 此抽象类无抽象方法,主要是为了不能实例化该类对象,确保模式完整性.
    /// 具体实施:
    /// 1.声明委托
    /// 2.声明委托类型事件
    /// 3.提供触发事件的方法
    /// </summary>
    public abstract class ModelBase
    {
    /// <summary>
    /// 声明一个委托,用于代理一系列"无返回"及"不带参"的自定义方法
    /// </summary>
    public delegate void SubEventHandler(); /// <summary>
    /// 声明一个绑定于上行所定义的委托的事件
    /// </summary>
    public event SubEventHandler SubEvent; /// <summary>
    /// 封装了触发事件的方法
    /// 主要为了规范化及安全性,除观察者基类外,其派生类不直接触发委托事件
    /// </summary>
    protected void Notify()
    {
    //提高执行效率及安全性
    if (SubEvent != null)
    SubEvent();
    }
    }
    }
  2. 建立观察者基类(单行为,多行为)
    //--------------------单行为--------------------- 
    
    namespace DelegateEvent
    {
    /// <summary>
    /// 在Observer Pattern(观察者模式)中,此类作为所有Observer(观察者)的抽象基类
    /// 所有要充当观察者的类(在此事例中为"老鼠"和"人")都继承于此类.
    /// 我们说此类作为观察者基类,用于规划所有观察者(即订阅方)订阅行为.
    /// 在此事例中,规划了针对目标基类(ModelBase)中声明的"无参无返回"委托的一个
    /// 方法(Response),并于构造该观察者时将其注册于具体目标(参数传递)的委托事件中.
    /// 具体实施过程:
    /// 1.指定观察者所观察的对象(即发布方).(通过构造器传递)
    /// 2.规划观察者自身需要作出响应方法列表
    /// 3.注册需要委托执行的方法.(通过构造器实现)
    /// </summary>
    public abstract class Observer
    {
    /// <summary>
    /// 构造时通过传入模型对象,把观察者与模型关联,并完成订阅.
    /// 在此确定需要观察的模型对象.
    /// </summary>
    /// <param name="childModel">需要观察的对象</param>
    protected Observer(ModelBase childModel)
    {
    //订阅
    //把观察者行为(这里是Response)注册于委托事件
    childModel.SubEvent += new ModelBase.SubEventHandler(Response);
    } /// <summary>
    /// 规划了观察者的一种行为(方法),所有派生于该观察者基类的具体观察者都
    /// 通过覆盖该方法来实现作出响应的行为.
    /// </summary>
    public abstract void Response();
    }
    }
    //-------------------多行为-------------------
    
    namespace DelegateEvent
    {
    /// <summary>
    /// 定义了另一个观察者基类.该观察者类型拥有两个响应行为.
    /// 并在构造时将响应行为注册于委托事件.
    /// (具体描述请参照另一观察者基类Observer)
    /// </summary>
    public abstract class Observer2
    {
    /// <summary>
    /// 构造时通过传入模型对象,把观察者与模型关联,并完成订阅.
    /// 在此确定需要观察的模型对象.
    /// </summary>
    /// <param name="childModel">需要观察的对象</param>
    protected Observer2(ModelBase childModel)
    {
    //订阅
    //把观察者行为(这里是Response和Response2)注册于委托事件
    childModel.SubEvent += new ModelBase.SubEventHandler(Response);
    childModel.SubEvent += new ModelBase.SubEventHandler(Response2);
    } /// <summary>
    /// 规划了观察者的二种行为(方法),所有派生于该观察者基类的具体观察者都
    /// 通过覆盖该方法来实现作出响应的行为.
    /// </summary>
    public abstract void Response(); public abstract void Response2();
    }
    }
  3. 建立具体目标
    using System;
    
    namespace DelegateEvent
    {
    /// <summary>
    /// 此类为观察者模式中的具体目标(即具体发布方),其继承于模型.
    /// 其中包含(调用)了在模型中被封装好的触发委托事件的方法.
    /// </summary>
    public class Cat : ModelBase
    {
    /// <summary>
    /// 定义了猫的一种行为----大叫
    /// </summary>
    public void Cry()
    {
    Console.WriteLine("Cat Cry..");
    //调用了触发委托事件的方法.
    //通知委托开始执行观察者已订阅的方法.
    this.Notify();
    }
    }
    }
  4. 建立具体观察者
    //--------------具体观察者(老鼠)-------------
    
    using System;
    
    namespace DelegateEvent
    {
    /// <summary>
    /// 此类为观察者模式中的具体观察者(即具体发布方),其继承于观察者基类.
    /// 其中覆盖了观察者基类规划好的方法,实现了响应的具体行为.
    /// </summary>
    public class Mouse : Observer
    {
    /// <summary>
    /// 观察者可以拥有自己的成员(字段或者方法).
    /// 在此事例中增加了"老鼠的名字"
    /// </summary>
    private readonly string _name; /// <summary>
    /// 构造时确定观察者所需要观察的对象(具体目标),并传递给观察者基类构造器,
    /// 实现响应行为(方法)的订阅.另外,为观察者实例初始化成员.
    /// </summary>
    /// <param name="name">老鼠的名字</param>
    /// <param name="childModel">
    /// 需要观察的对象(发布方).
    /// 此处用模型基类来传递,是为了兼容所有派生于此模型的观察者,从而提高扩展性.
    /// </param>
    public Mouse(string name, ModelBase childModel)
    : base(childModel)
    {
    //初始化字段(老鼠的名字)
    _name = name;
    } /// <summary>
    /// 覆盖了该类观察者需要作出的具体响应行为.
    /// 此行为已在观察者基类中注册于委托事件,由委托事件调度执行,不需要直接调用.
    /// </summary>
    public override void Response()
    {
    //具体响应内容
    Console.WriteLine(_name + "开始逃跑");
    }
    }
    }
    //----------------具体观察者(主人)----------------
    
    using System;
    
    namespace DelegateEvent
    {
    /// <summary>
    /// 此类为观察者模式中的具体观察者(即具体发布方),其继承于观察者基类.
    /// 其中覆盖了观察者基类规划好的方法,实现了响应的具体行为.
    /// </summary>
    public class Master : Observer
    {
    /// <summary>
    /// 构造时确定观察者所需要观察的对象(具体目标),并传递给观察者基类构造器,
    /// 实现响应行为(方法)的订阅.
    /// </summary>
    public Master(ModelBase childModel)
    : base(childModel)
    {
    } /// <summary>
    /// 覆盖了该类观察者需要作出的具体响应行为.
    /// 此行为已在观察者基类中注册于委托事件,由委托事件调度执行,不需要直接调用.
    /// </summary>
    public override void Response()
    {
    Console.WriteLine("主人醒来");
    }
    }
    }
    //-------------------具体观察者(宝宝)-------------
    
    using System;
    
    namespace DelegateEvent
    {
    /// <summary>
    /// 此类为观察者模式中的具体观察者(即具体发布方),其继承了订阅了2个响应行为的
    /// 观察者基类.
    /// 其中覆盖了观察者基类规划好的二个方法,实现了响应的具体行为.
    /// </summary>
    public class Master2 : Observer2
    {
    /// <summary>
    /// 构造时确定观察者所需要观察的对象(具体目标),并传递给观察者基类构造器,
    /// 实现响应行为(方法)的订阅.
    /// </summary>
    public Master2(ModelBase childBase)
    : base(childBase)
    {
    } /// <summary>
    /// 覆盖了该类观察者需要作出的具体响应行为.
    /// 此行为已在观察者基类中注册于委托事件,由委托事件调度执行,不需要直接调用.
    /// </summary>
    public override void Response()
    {
    Console.WriteLine("baby醒来。。。。");
    } /// <summary>
    /// 覆盖了该类观察者需要作出的另一个响应行为.
    /// </summary>
    public override void Response2()
    {
    Console.WriteLine("开始哭闹。。。。。");
    }
    }
    }
  5. 运行测试
    using System;
    
    namespace DelegateEvent
    {
    /// <summary>
    /// Observer Pattern(观察者模式)事例分析
    /// 题目:猫大叫,两只老鼠开始逃跑,主人醒来,宝宝也醒来了并且哭了起来.
    /// 关于目标(发布方):
    /// 在此事例中,只有一个目标对象(发布方)猫,因为其他全部实体的行为都是
    /// 响应它的"大叫"所执行的.猫是主动方,它的大叫引起一系列的连锁反应.
    /// 关于观察者(订阅方):
    /// 至于此事例的中的观察者分别有两大类,一类是听到猫大叫后只作出一种
    /// 反应的观察者(老鼠,主人),另一类是听到锚大叫后会作出两种响应的观察者(
    /// 宝宝).所以观察者分别需要派生于两个不同的观察者基类.
    /// </summary>
    public class SubMain
    {
    public static void Main()
    {
    //声明并实例化一个目标(即发布方)对象----猫
    var myCat = new Cat();
    //声明并实例化一个Mouse类型的观察者对象--名叫mouse1的老鼠.并把那只猫作为它所要观察的对象.
    var myMouse1 = new Mouse("mouse1", myCat);
    //类似地生成另一只名叫mouse2的老鼠(观察者),把同一只猫作为它的观察的对象.
    var myMouse2 = new Mouse("mouse2", myCat);
    //声明并实例化一个Master类型的观察者--主人,并同时把那只猫也作为他的观察对象.
    var myMaster = new Master(myCat);
    //声明并实例化一个Master2类型的观察者--宝宝,同时把那只猫也
    var myLittleMaster = new Master2(myCat); //猫大叫,并触发了委托事件,从而开始按顺序调用观察者已订阅的方法.
    myCat.Cry(); Console.Read();
    }
    }
    }

c#设计模式-观察者模式的更多相关文章

  1. 18. 星际争霸之php设计模式--观察者模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

  2. linkin大话设计模式--观察者模式

    linkin大话设计模式--观察者模式 观察者模式定义了对象间的一对多依赖关系,让一个或者多个观察者观察一个对象主题.当主题对象的状态发生改变的时候,系统能通知所有的依赖于此对象的观察者对象,从而能自 ...

  3. java设计模式--观察者模式(Observer)

    java设计模式--观察者模式(Observer) java设计模式--观察者模式(Observer) 观察者模式的定义: 定义对象间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖于它的 ...

  4. js设计模式-观察者模式

    定义: 观察者模式又叫发布订阅模式,它定义了对象间的一种一对多的依赖关系.观察者模式让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响他们之间的互相通信. 思路 定义一个对象,在对象中实 ...

  5. 【设计模式】Java设计模式 - 观察者模式

    [设计模式]Java设计模式 - 观察者模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 @一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长 ...

  6. [Head First设计模式]山西面馆中的设计模式——观察者模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 引言 不知不自觉又将设计模式融入生活了,吃个饭也不得安生,也发现生活中的很多场景,都可以用设计模式来模拟.原来设计模式就在 ...

  7. javascript 设计模式-----观察者模式

    观察者模式在设计模式中被重点提到,因为它应用的场景非常多,而且在模块化设计当中扮演着非常重要的角色.MVC模式中最底层的就是观察者模式,当下流行的javascript框架backbone就是很好地运用 ...

  8. JAVA 设计模式 观察者模式

    用途 观察者模式 (Observer) 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象. 这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己. 观 ...

  9. IOS设计模式-观察者模式

    前言:23种软件设计模式中的观察者模式,也是在软件开发中,挺常用的一种设计模式.而在苹果开发中,苹果Cocoa框架已经给我们实现了这个设 计模式,那就是通知和KVO(Key-Value Observi ...

随机推荐

  1. js 正则表达式

    //判断字符串是否为数字 function checkRate(input) { var re = /^[0-9]+.?[0-9]*$/; if (!re.test(input.rate.value) ...

  2. Unity IOC容器的简单应用(转)

    转自:http://blog.csdn.net/wanzhuan2010/article/details/7763280 Unity是Unity是微软patterns& practices组用 ...

  3. WPF 图片显示中的保留字符问题

    在WPF中显示一张图片,本是一件再简单不过的事情.一张图片,一行XAML代码即可. 但是前段时间遇到了一件奇怪的事: 开发机上运行正常的程序,在某些客户机器上却显示不了图片,而且除了这个问题,其它运行 ...

  4. Codeforces #380 div2 C(729C) Road to Cinema

    C. Road to Cinema time limit per test 1 second memory limit per test 256 megabytes input standard in ...

  5. IQueryable<T> 与 ObjectQuery<T> 差异

    命名空间:System.Data.Objects程序集:  System.Data.Entity(在 System.Data.Entity.dll 中)public class ObjectQuery ...

  6. Await, and UI, and deadlocks! Oh my!

    It’s been awesome seeing the level of interest developers have had for the Async CTP and how much us ...

  7. docker pull certification error

    export DOMAIN_NAME=<my-dtr-domain> echo -n | openssl s_client -showcerts -connect itapregistry ...

  8. 如何为Eclipse安装主题(Color Theme)

    Eclipse开发环境默认都是白底黑字的,看到同事的Xcode中设置的黑灰色背景挺好看的,就去网上查了一下.发现Eclipse也可以设置主题. 方法1:你可以从Eclipse Marketplace中 ...

  9. 如何禁用Marlin温度保护

    最近在玩3D打印,搞了套MEGA 2560 + RAMPS 1.4 + A4988,刷Marlin(https://github.com/MarlinFirmware/Marlin)固件,接上电机调试 ...

  10. Boyer-Moore 字符串匹配算法

    字符串匹配问题的形式定义: 文本(Text)是一个长度为 n 的数组 T[1..n]: 模式(Pattern)是一个长度为 m 且 m≤n 的数组 P[1..m]: T 和 P 中的元素都属于有限的字 ...