无废话C#设计模式之十九:Observer

意图

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

场景

这次不说游戏了,假设我们需要在一个Web页面上分页显示数据。首先需要一个分页控制器和一个显示数据的表格。开始,客户的需求很简单,需要两个向前翻页向后翻页的按钮作为控制器,还需要一个GridView来显示数据。你可能会这么做:

l         在页面上放两个按钮和一个GridView控件

l         点击了下一页按钮判断是否超出了页面索引,如果没有的话更新GridView中的数据,然后更新控件的当前页,如果翻页后是最后一页的话把下一页按钮设置为不可用。

l         点击了上一页按钮判断是否超出了页面索引,如果没有的话更新GridView中的数据,然后更新控件的当前页,如果翻页后是第一页的话把上一页按钮设置为不可用。

在这里,我们的翻页控件仅仅和GridView进行依赖,看似问题不大。没有想到,客户看了Demo后觉得这样的体验不好,希望在页面上呈现当前页和总共页。于是,我们又在页面上加了一个Label控件,在按钮的点击事件里面再去更新Label控件的值。客户挺满意的,随着软件中数据越来越多,总页数达到了几十页,客户觉得前后翻页太不合理的了,希望有一个显示页数列表的分页控制器,客户的这个请求彻底使我们晕了,代码被我们修改的非常混乱:

l         点击了列表分页控件的页数后更新自身状态、通知GridView加载数据、通知按钮分页控件更新自身状态、通知Label更新页数信息。

l         点击了按钮分页控件后更新自身状态、通知GridView加载数据、通知列表分页控件更新自身状态、通知Label更新页数信息。

如果今后页面上还需要针对分页功能有任何修改的话,真不知道怎么去改。由此引入观察者模式来解决这些问题。

示例代码

using System;

using System.Collections.Generic;

using System.Text;

namespace ObserverExample

{

    class Program

    {

        static void Main(string[] args)

        {

            ButtonPager buttonPager = new ButtonPager();

            ListPager listPager = new ListPager();

            Control gridview = new GridView();

            Control label = new Label();

            buttonPager.changePageHandler += new Pager.ChangePageHandler(buttonPager.ChangePage);

            buttonPager.changePageHandler += new Pager.ChangePageHandler(gridview.ChangePage);

            buttonPager.changePageHandler += new Pager.ChangePageHandler(label.ChangePage);

            buttonPager.changePageHandler += new Pager.ChangePageHandler(listPager.ChangePage);

            listPager.changePageHandler += new Pager.ChangePageHandler(buttonPager.ChangePage);

            listPager.changePageHandler += new Pager.ChangePageHandler(gridview.ChangePage);

            listPager.changePageHandler += new Pager.ChangePageHandler(label.ChangePage);

            listPager.changePageHandler += new Pager.ChangePageHandler(listPager.ChangePage);

            buttonPager.NextPage();

            Console.WriteLine();

            buttonPager.NextPage();

            Console.WriteLine();

            buttonPager.NextPage();

            Console.WriteLine();

            buttonPager.PreviousPage();

            Console.WriteLine();

            buttonPager.PreviousPage();

            Console.WriteLine();

            listPager.SelectPage(2);

            Console.WriteLine();

            listPager.SelectPage(1);

            Console.WriteLine();

            listPager.SelectPage(0);

        }

    }

   

    abstract class Pager

    {

        protected int pageIndex = 0;

        public int PageIndex

        {

            get { return pageIndex; }

            set { pageIndex = value; }

        }

        protected int pageCount = 3;

        public int PageCount

        {

            get { return pageCount; }

        }

        public event ChangePageHandler changePageHandler;

        public delegate void ChangePageHandler(Pager sender);

       

        protected void ChangePage()

        {

            if (changePageHandler != null)

                changePageHandler(this);

        }

    }

    class ButtonPager : Pager, Control

    {

        public void NextPage()

        {

            if (pageIndex < pageCount - 1)

            {

                Console.WriteLine("Click NextPage Button...");

                pageIndex++;

                ChangePage();

            }

        }

        public void PreviousPage()

        {

            if (pageIndex > 0)

            {

                Console.WriteLine("Click PreviousPage Button...");

                pageIndex--;

                ChangePage();

            }

        }

        public void ChangePage(Pager sender)

        {

            base.pageIndex = sender.PageIndex;

            if (pageIndex > 0 && pageIndex < pageCount - 1)

                Console.WriteLine("<<Previous Next>>");

            else if (pageIndex == 0)

                Console.WriteLine("Next>>");

            else

                Console.WriteLine("<<Previous");

        }

    }

    class ListPager : Pager, Control

    {

        public void SelectPage(int pageIndex)

        {

            if (pageIndex >= 0 && pageIndex < pageCount)

            {

                Console.WriteLine(string.Format("Click <{0}> Link...", pageIndex + 1));

                base.pageIndex = pageIndex;

                ChangePage();

            }

        }

        public void ChangePage(Pager sender)

        {

            base.pageIndex = sender.PageIndex;

            for (int i = 1; i <= pageCount; i++)

            {

                if (pageIndex + 1 == i)

                    Console.Write(string.Format(" <{0}> ", i));

                else

                    Console.Write(string.Format(" {0} ", i));

            }

            Console.WriteLine();

        }

    }

    interface Control

    {

        void ChangePage(Pager sender);

    }

    class GridView : Control

    {

        public void ChangePage(Pager sender)

        {

            Console.WriteLine(string.Format("GridView->Show data of page {0}", sender.PageIndex + 1));

        }

    }

    class Label : Control

    {

        public void ChangePage(Pager sender)

        {

            Console.WriteLine(string.Format("Label.Text=[{0}/{1}]", sender.PageIndex + 1, sender.PageCount));

        }

    }

}

代码执行结果如下图:

 

代码说明

l         在这里,我们使用C#语言事件机制来实现观察者模式,虽然和GOF的“标准”模式不同,但是还是可以看出观察者模式最基本的几个角色。要知道,GOF设计模式虽然是经典,但是毕竟是很久以前提出的,可以考虑使用C#的一些特性来改进。

l         Pager类型是抽象主体角色(或者叫作被观察者、发布方、主动方、目标、主题),传统的抽象主体用于保存观察者。在这里的ChangePage方法用于在有变化后触发事件。另外,从ChangePageHandler代理中看到,我们把抽象主体作为了参数,这样,观察者就能根据主体的状态作一些调整。

l         ButtonPage是一个具体主体角色。NextPage()方法中首先判断请求的页面是否超过了页面索引,如果没有超过的话,则更新页面索引并且调用了基类的ChangePage()方法来通知所有的观察者。PreviousPage()方法也是一样的道理。

l         Control接口是一个抽象观察者角色(或者说观察者、订阅方、被动方),它定义了一个统一的接口,如果接受到了事件通知,则调用这个方法进行处理。

l         GridView和Label则是具体观察者,可以看到它们不用考虑怎么被通知的事情,只需要考虑被通知后做什么。在这里,GridView重新绑定了数据,Label显示了页数信息。

l         这样其实已经组成了一个最基本的观察者模式的结构。获取你也注意到了,ButtonPager还实现了Control接口,说明它还是一个具体的观察者。这并没有什么不可以,它一方面可以在翻页后通知GridView、Label等对象,一方面又可以被别人通知。还记得客户需要实现一个ListPager的需求吗?在ListPager翻页后还需要通知ButtonPager来改变状态呢。

l         一样的道理,ListPager也是一个观察者。它需要观察ButtonPager的变动。

l         注意到在ListPager和ButtonPager的ChangePage()方法中都更新了页面的索引值,你或许不理解为什么Label和GridView不更新呢?其实,这并没有什么奇怪,ButtonPager翻页后通知ListPager更新状态,最需要更新的状态就是页面索引值,用户不是直接点击ListPager翻页的,当然需要更新。Label和GridView中并没有实现是因为我们并没有实现具体的一些细节,在实际应用中这些控件保存一些状态也不奇怪。

l         最后来看一看怎么牵线搭桥。我们在ButtonPager的改变页面状态事件中注册了四个代理,也就是说它改变状态后需要通知四个观察者。怎么是四个呢?还包括它自己,从逻辑上可能难以理解,其实这是可行的重用代码的方案。对ButtonPager来说,是点击哪个控件翻页的并不重要,作为主体它的责任就是通知观察者,作为观察者它的责任就是更新状态或说对事件作出响应。

l         此例完整了一个四个观察者、两个主体的观察者模式。你可能角色一个类型既是观察者又是主体不可理解,其实这在现实生活中非常多的,生物链中的大部分生物既是观察者又是主体,“螳螂捕蝉,黄雀在后”中的螳螂就是。

l         再谈谈耦合和扩展。要再增加一个下拉框分页的分页控件怎么办?无须修改原来的代码,再写一个DropDownPager(继承Pager,实现Control),并且为它的修改分页事件和所有观察者挂钩就可以了。要再增加一个ListBox控件针对不同页数显示不同数据怎么办?也无须修改原来的代码,再写一个ListBox控件(实现Control),实现翻页响应的方法,并且订阅所有Pager的翻页事件即可。

l         注意,本例仅仅用来演示观察者模式的结构,并没有遵循.NET事件模型的最佳实践。

何时采用

通过这个例子,我们就很容易理解观察者模式的适用点了:

l         一个对象的行为引发其它多个对象的行为。前者成为主体,后者称为观察者。

l         为了降低耦合,不希望主体直接调用观察者的方法,而是采用动态订阅主体事件的方式来进行自动的连锁响应行为。

l         为了增加灵活性,希望动态调整订阅主体事件的观察者,或者希望动态调整观察者订阅主体的事件。

实现要点

l         抽象主体角色公开了自身的事件,可以给任意观察者订阅。

l         抽象观察者角色定义了统一的处理行为,在C#中使用事件-代理模式的话,统一的处理行为并不这么重要,有的时候甚至还会限制灵活性。由于本例的特殊原因,并没有从这个接口中得益。

l         响应方法订阅代理事件的操作可以在观察者中定义也可以在外部定义,根据自己的需求决定,放在外部定义灵活性更高。

l         具体观察者往往只需要实现响应方法即可。

l         可以有多个主体角色、多个观察者角色交错,也可以一个类型是两个角色,主体也可以提供多个事件。从应用上来说观察者模式变化是非常多的。

注意事项

 

l         由于这种灵活性,在观察者订阅事件的时候需要考虑是否会出现破坏行为?是否会出现无限循环或死锁等问题?观察者响应的时候是否会影响其它观察者?

l         对于观察者数量很多的时候使用观察者模式并不适合,可能会造成性能问题。

l         在不能采用事件-代理方式完成观察者模式的情况下(比如跨网络应用等)可以考虑采用传统的观察者模式。

转载于:https://www.cnblogs.com/lovecherry/archive/2007/10/20/931226.html

(原创)无废话C#设计模式之十九:Observer相关推荐

  1. (原创)无废话C#设计模式之十二:Bridge

    无废话C#设计模式之十二:Bridge 意图 将抽象部分与实现部分分离,使它们都可以独立的变化. 场景 还是说我们要做的网络游戏,多个场景需要扩充的问题我们已经采用了创建型模式来解决.现在的问题就是, ...

  2. (原创)无废话C#设计模式之二十二:总结(针对GOF23)

    无废话C#设计模式之二十二:总结(针对GOF23) 比较 设计模式 常用程度 适用层次 引入时机 结构复杂度 Abstract Factory 比较常用 应用级 设计时 比较复杂 Builder 一般 ...

  3. (原创)无废话C#设计模式之十一:Composite

    无废话C#设计模式之十一:Composite 意图 将对象组合成树形结构以表示"部分-整体"的层次结构.Composite模式使得用户对单个对象和组合对象的使用具有一致性. 场景 ...

  4. (原创)无废话C#设计模式之十七:Chain Of Resp.

    无废话C#设计模式之十七:Chain Of Resp. 意图 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象能处理 ...

  5. (原创)无废话C#设计模式之四:Factory Method

    无废话C#设计模式之四:Factory Method       <a href='http://xgb.xgb.cc'>a</a>   <a href='http:// ...

  6. 【原创】StreamInsight查询系列(十九)——查询模式之检测异常

    上篇文章介绍了查询模式中如何发现趋势,这篇博文将介绍StreamInsight中如何检测异常. 测试数据准备 为了方便测试查询,我们首先准备一个静态的测试数据源: var now = DateTime ...

  7. Golang观察者设计模式(十九)

    github:https://github.com/zhumengyifang/GolangDesignPatterns 上一篇:https://blog.csdn.net/weixin_401651 ...

  8. 初学 Java 设计模式(十九):实战观察者模式 「您的快递已到达蜂站,请及时签收」

    一.观察者模式介绍 1. 解决的问题 主要解决将一个对象的状态改变通知给其他对象的问题. 2. 定义 观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个 "观察 ...

  9. php 状态模式,PHP设计模式(十九)—状态模式 (State Pattern)

    状态模式 (State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类.其别名为状态对象(Objects for States) (一)为什么需要状态模式 ...

最新文章

  1. OpenCV中的内存泄露问题(cvLoadImage,cvCloneImage)
  2. 图灵71年前就已提出神经网络!《智能机器》再掀热议却一生未发表
  3. matlab 人群仿真,用simulink如何实现人群搜索算法的pid参数整定
  4. oc35--自定义构造方法
  5. Linux 下 RMAN无反应问题处理
  6. 团队项目——ASC Master
  7. 日常生活小技巧 -- win10造字
  8. 实录分享 | 计算未来轻沙龙:图神经网络前沿研讨会
  9. postsharp初体验
  10. android 创建文件夹_Android 动画小记
  11. ios学习笔记——RunTime
  12. 作者:​冯景华(1984-),男,国家超级计算天津中心主任助理、系统管理部部长。...
  13. SpringCloudStream整合rabbitMq
  14. JS、CSS合并带来的效率提升
  15. spring boot admin 自定义
  16. 用window.showModalDialog()实现DIV模式弹出窗口
  17. Rayman的绝顶之路——Leetcode每日一题打卡2
  18. 关于大学生是否沉迷游戏的报告
  19. 医学图像预处理(五) 器官与病灶的直方图
  20. 计算机通信与网络(一)

热门文章

  1. python中异或怎么算_python中的异或运算
  2. 注册app短信验证平台_怎样挑选网站验证码短信平台?
  3. wireguard握手报文结构
  4. ArcGIS Maritime Server 开发教程(四)Maritime Service 开发实践
  5. wamp 403 禁止访问
  6. (转)LuaPlus C++ 函数互调
  7. r 语言 ggplot上添加平均值_技术贴 | R语言:ggplot堆叠图、冲积图、分组分面、面积图...
  8. 第6章 基于锁的并发数据结构设计
  9. 科恒khs202温控器使用说明书_STC-9200温控器使用说明书——精创温控器
  10. ✨Synchronized底层实现---偏向锁