重温Observer模式热水器改.docx

上传人:b****8 文档编号:11092561 上传时间:2023-02-25 格式:DOCX 页数:16 大小:71.18KB
下载 相关 举报
重温Observer模式热水器改.docx_第1页
第1页 / 共16页
重温Observer模式热水器改.docx_第2页
第2页 / 共16页
重温Observer模式热水器改.docx_第3页
第3页 / 共16页
重温Observer模式热水器改.docx_第4页
第4页 / 共16页
重温Observer模式热水器改.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

重温Observer模式热水器改.docx

《重温Observer模式热水器改.docx》由会员分享,可在线阅读,更多相关《重温Observer模式热水器改.docx(16页珍藏版)》请在冰豆网上搜索。

重温Observer模式热水器改.docx

重温Observer模式热水器改

重温Observer模式--热水器·改

引言

在C#中的委托和事件一文的后半部分,我向大家讲述了Observer(观察者)模式,并使用委托和事件实现了这个模式。

实际上,不使用委托和事件,一样可以实现Observer模式。

在本文中,我将使用GOF的经典方式,再次实现一遍Observer模式,同时将讲述在C#中的委托和事件一文中没有提及的推模式(Push)和拉模式(Pull)。

设计思想概述

在C#中的委托和事件一文后半部分中我已经较详细的讲述了Observer设计模式的思想,所以这里仅简单的提及一下。

Observer设计模式中实际上只包含了两类对象,一个是Subject(主题),一个是Observer(观察者)。

它们之间的角色是:

∙Subject:

主题(被监视对象),它往往包含着Observer所感兴趣的内容。

∙Observer:

观察者,它观察Subject。

当Subject中的某件事发生的时候(通常是它所感兴趣的内容改变的时候),会被自动告知,而Observer则会采取相应的行动(通常为更新自身状态或者显示输出)。

它们之间交互的核心工作流程就是:

1.Subject提供方法,比如Register()和UnRegister(),用于Observer进行注册(表示它对Suject感兴趣)和取消注册(不再对它感兴趣)。

∙Register()方法实现为:

它接收一个Observer的引用作为参数,并保存此引用。

∙保存的方式通常为在Subject内声明一个集合类,比如:

List

∙一个Subject可以供多个Observer注册。

2.调用Subject实例的Register()方法,并将一个Observer的引用传递进去。

3.Observer包含一个Update()方法,此方法供Subject(通过保存的Observer的引用)以后调用。

4.Subject包含一个Notify()方法,当某件事发生时,调用Notify(),通知Subject。

∙Notify的方法实现为:

遍历保存Observer引用的集合类,然后在Observer的引用上调用Update方法,更新Observer。

∙某件事是一个不确定的事,对于热水器来说,这个事就是“温度达到一定高度”。

它对外界暴露的方法,应该是“烧水”--BoilWater(),而不是Notify(),所以Notify通常实现为私有方法。

Observer向Subject注册的序列图表示如下:

Subject事件触发时,通知Observer调用Update()方法的序列图如下:

模式的接口定义

按照面向对象设计的原则:

面向接口编程,而非面向实现编程。

那么现在应该首先定义Subject和Observer的接口,我们可能很自然地会想到将这两个接口分别命名为ISubjcet和IObserver。

而实际上,据我查阅的一些资料,这里约定俗成的命名为:

IObservable和IObserver,其中由Subject实现IObservable。

NOTE:

可能很多人和我当初一样困惑,命名为ISubject不是很好么,为什么叫IObservable?

我参考了一些资料,大概的解释是这样的:

接口定义的是一个行为,表示的是一种能力,所以对于接口的命名最好用动词的形容词或者名词变体。

这里,Observe是一个动词,意为观察,Observer是动词的名词变体,意为观察者;Observable是动词的形容词变体,表示为可观察的。

类似的例子有很多,比如IComparable和IComparer接口、IEnumerable和IEnumerator接口等。

现在我们先来看Subject需要实现的接口IObservable。

IObservable接口

首先创建解决方案ObserverPattern,并在其下添加控制台项目ConsoleApp,然后假如IObservable.cs文件,来完成这个接口。

如同我们上面分析的,Suject将实现这个接口,它只用定义两个方法Register()和Unregister:

publicinterfaceIObservable{

   voidRegister(IObserverobj);      //注册IObserver

   voidUnregister(IObserverobj);    //取消IObserver的注册

}

注意它的两个方法接收IObserver类型的对象,分别用于注册和取消注册。

IObserver接口

现在我们再来完成IObserver接口,所有的Observer都需要实现这个接口,以便在事件发生时能够被自动告知(自动调用其Update()方法,改变自身状态),它仅包含一个Update()方法:

publicinterfaceIObserver{

   voidUpdate();     //事件触发时由Subject调用,更新自身状态

}

再强调一遍,这里的关键就是Update()方法不是由Observer本身调用,而是由Subject在某事发生时调用。

抽象基类SubjectBase

注意到上面序列图中的Container(容器),它用于保存IObserver引用的方式,对于很多IObservable的实现来说可能都是一样的,比如说都用List或者是Hashtable等。

所以我们最好再定义一个抽象类,让它实现IObservable接口,并使用List作为容器的一个默认实现,以后我们再创建实现IObservalbe的类(Subject),只需要继承这个基类就可以了,这样可以更好地代码重用:

publicabstractclassSubjectBase:

IObservable{

   //使用一个List作为IObserver引用的容器

   privateListcontainer=newList();

   

   publicvoidRegister(IObserverobj){

      container.Add(obj);

   }

   publicvoidUnregister(IObserverobj){

      container.Remove(obj);

   }

   protectedvirtualvoidNotify(){      //通知所有注册了的Observer

      foreach(IObserverobserverincontainer){

          observer.Update();          //调用Observer的Update()方法

      }

   }

}

有了这样两个接口,一个抽象类我们的UML类图便可以画出来了:

注意这里也可以不使用IObservable接口,直接定义一个抽象类,定义IObservable接口能进一步的抽象,更灵活一些,可以基于这个接口定义出不同的抽象类来(主要区别为Container的实现不同,可以用其他的集合类)。

Observer模式的实现

现在我们来实现Observer模式,我们先创建我们的实体类(ConcreteClass):

热水器(Heater),报警器(Alarm),显示器(Screen)。

其中,热水器是Subject,报警器和显示器是Observer。

报警器和显示器关心的东西是热水器的水温,当热水器的水温大于97度时,显示器需要显示“水快烧开了”,报警器发出声音,也提示“嘟嘟嘟,水快烧开了”。

下面的代码非常的简单明了,也添加了注释,我就不做说明了:

热水器(Subject)的实现

热水器继承自SujectBase基类,并添加了BoilWater()方法。

publicclassHeater:

SubjectBase{

   privatestringtype;             //添加型号作为演示

   privatestringarea;             //添加产地作为演示

   privateinttemprature;        //水温

   publicHeater(stringtype,stringarea){

      this.type=type;

      this.area=area;

      temprature=0;

   }

   publicstringType{get{returntype;}}

   publicstringArea{get{returnArea;}}

   publicHeater():

this("RealFire001","ChinaXi'an"){}

   //供子类覆盖,以便子类拒绝被通知,或添加额外行为

   protectedvirtualvoidOnBoiled(){

      base.Notify();//调用父类Notify()方法,进而调用所有注册了的Observer的Update()方法

   }

   publicvoidBoilWater(){      //烧水

      for(inti=0;i<=99;i++){

          temprature=i+1;

          if(temprature>97){      //当水快烧开时(温度>97度),通知Observer

             OnBoiled();

          }

      }

   }

}

报警器和显示器(Observer)的实现

报警器(Alarm)和显示器(Screen)的实现是类似的,仅仅为了说明多个Observer可以注册同一个Subject。

//显示器

publicclassScreen:

IObserver{

   //Subject在事件发生时调用,通知Observer更新状态(通过Notify()方法)

   publicvoidUpdate(){

      Console.WriteLine("Screen".PadRight(7)+":

水快烧开了。

");

   }

}

//报警器

publicclassAlarm:

IObserver{

   publicvoidUpdate(){

      Console.WriteLine("Alarm".PadRight(7)+":

嘟嘟嘟,水温快烧开了。

");

   }

}

运行程序

接下来,我们运行一下程序:

classProgram{

   staticvoidMain(string[]args){

      Heaterheater=newHeater();

      Screenscreen=newScreen();

      Alarmalarm=newAlarm();

      heater.Register(screen);    //注册显示器

      heater.Register(alarm);        //注册热水器

      heater.BoilWater();            //烧水

      heater.Unregister(alarm);   //取消报警器的注册

      Console.WriteLine();

      heater.BoilWater();            //再次烧水

   }

}

输出为:

Screen:

水快烧开了。

Alarm :

嘟嘟嘟,水快烧开了。

Screen:

水快烧开了。

Alarm :

嘟嘟嘟,水快烧开了。

Screen:

水快烧开了。

Alarm :

嘟嘟嘟,水快烧开了。

Screen:

水快烧开了。

Screen:

水快烧开了。

Screen:

水快烧开了。

推模式和拉模式

像上面这种实现方式,基本上是没有太大意义的。

比如说,我们通常会希望在Screen上能够即时地显示水的温度,而且当水在100度的时候显示“水已经烧开了”,而非“水快烧开了”。

我们还可能希望显示热水器的型号和产地。

所以我们需要在Observer的Update()方法中能够获得Subject中所发生的事件的进展状况或者事件触发者Suject的状态和属性。

在本例中事件的进展状况,就是水的温度;事件触发者(Suject)的状态和属性,则为热水器的型号和产地。

此时,我们有两种策略,一种是推模式,一种是拉模式,我们先看看推模式。

Observer中的推模式

顾名思义,推模式就是Subject在事件发生后,调用Notify时,将事件的状况(水温),以及自身的属性(状态)封装成一个对象,推给Observer。

而如何推呢?

当然是通过Notify()方法,让Notify()方法接收这个对象,在Notify()方法内部,再次将对象传递给Update()方法了。

那么现在要做两件事:

1、创建新类型,这个类型封装了我们想要推给Observer(显示器)的事件进展状况(水温),以及事件触发者Subject(热水器)的属性(或者叫状态)。

我们在ObserverPattern解决方案下重新建一个控制台项目,起名为ConsoleApp2,并设置为启动项目。

将上一项目ConsoleApp中的文件复制进来,然后我们创建一个新类型BoiledEventArgs,用它来封装我们推给Observer的数据。

publicclassBoiledEventArgs{

   privateinttemperature;    //温度

   privatestringtype;        //类型

   privatestringarea;        //产地

   publicBoiledEventArgs(inttemperature,stringtype,stringarea){

      this.temperature=temperature;

      this.type=type;

      this.area=area;

   }

   publicintTemperature{get{returntemperature;}}

   publicstringType{get{returntype;}}

   publicstringArea{get{returnarea;}}

}

注意这个类型的命名虽然为BoiledEventArgs,但是和.Net中的内置类型EventArgs没有任何联系,只是起了这样一个名字。

2、我们需要依次修改IObserver接口,Screen类的Update()方法,SubjectBase类,以及Heater类,让他们可以接收这个EventArgs参数。

出于示范的目的,后面的例子我都将不再使用警报器Alarm类,它的存在仅仅是为了说明多个Observer可以注册一个Subject,上面我们已经示范过了,所以现在我们把它删掉。

我们先来看下IObserver接口:

publicinterfaceIObserver{

   //推模式的实现方式,接收一个BoiledEventArgs

   voidUpdate(BoiledEventArgse);

}

接口变了,显示器(Screen)的实现也需要修改:

publicclassScreen:

IObserver{

   privateboolisDisplayedType=false;     //标记变量,标示是否已经打印过

   publicvoidUpdate(BoiledEventArgse){

      //打印产地和型号,只打印一次

      if(!

isDisplayedType){

          Console.WriteLine("{0}-{1}:

",e.Area,e.Type);

          Console.WriteLine();

          isDisplayedType=true;

      }

      if(e.Temperature<100){  

          Console.WriteLine(

             String.Format("Alarm".PadRight(7)+":

水快烧开了,当前温度:

{0}。

",e.Temperature));

      }else{

          Console.WriteLine(

             String.Format("Alarm".PadRight(7)+":

水已经烧开了!

"));               

      }

   }

}

现在可以看到,在Update()方法中,通过传递进来的BoiledEventArgs参数,我们可以获得事件进展(温度),以及事件触发者的信息(产地和型号)了。

接下来我们看这个BoiledEventArgs是如何传递给Update()方法的,我们看下SubjectBase基类和热水器Heater需要做怎样的修改:

publicabstractclassSubjectBase:

IObservable{

   //其余略...

   protectedvirtualvoidNotify(BoiledEventArgse){   //通知所有注册了的Observer

      foreach(IObserverobserverincontainer){

          observer.Update(e);         //调用Observer的Update()方法

      }

   }

}

publicclassHeater:

SubjectBase{

   //其余略...

   //供子类覆盖,以便子类拒绝被通知,或者添加额外行为

   protectedvirtualvoidOnBoiled(BoiledEventArgse){

      base.Notify(e);         //调用基类方法,通知Observer

   }

   publicvoidBoilWater(){      //烧水

      for(inti=0;i<=99;i++){

          temprature=i+1;

          if(temperature>97){     //当水快烧开时(温度>97度),通知Observer

            BoiledEventArgse=newBoiledEventArgs(temperature,type,area);

             OnBoiled(e);

          }

      }

   }

}

我们看到,在事件发生时(水温>97度),我们根据事件进展状况和热水器的属性创建了BoiledEventArgs类型的实例,并且传递给了OnBoiled()方法,进而调用了基类的方法,传递了该实例。

我们再次对程序进行一下测试:

classProgram{

   staticvoidMain(string[]args){

      Heaterheater=newHeater();

      Screenscreen=newScreen();

      heater.Register(screen);    //注册显示器

      heater.BoilWater();            //烧水

   }

}

输出为:

ChinaXi'an-RealFire001:

Alarm :

水快烧开了,当前温度:

98。

Alarm :

水快烧开了,当前温度:

99。

Alarm :

水已经烧开了!

Observer中的拉模式

继续进行之前,我们在ObserverPattern解决方案下,再创建一个新的Console项目,命名为ConsoleApp3,然后把ConsoleApp2项目下的文件拷贝过来,把启动项目设置为ConsoleApp3。

拉模式的意思就是说,Subject(热水器)在事件发生时(水温超过97度),并非将自身状态封装成对象通过Notify()方法,进而再通过Observer的引用,调用Update()方法传递给Observer(显示器),而是直接将自身的引用(以基类或者Object的形式)传递过去。

Observer在Update()方法中,对传递进来的引用进行一个向下转换(Downcast),转换成具体的Subject类(比如热水器),然后通过这个引用调用Subject实体类(热水器)的公共属性获取状态信息(从中把有用数据拉出来:

-)。

我们需要再次对IObserver接口的Update()方法修改,相应的修改还要修改SubjectBase基类、Heater类以及IObserver接口的实现--显示器类(Screen)。

publicinterfaceIObserver{

   //拉模式的Update()方法定义

   voidUpdate(IObservablesender);

}

注意这里接收一个IObservable类型作为Update()方法的参数,而IObservable接口本身只包含Regesiter()和Unregister()两个方法,所以在IObserver的实现中,这里要进行向下转换,转换为响应的实体类对象,才能获得对象的属性。

这里也可以接受一个Object类型参数。

我们现在看这个接口的实现,显示器类(Screen):

publicclassScreen:

IObserver{

   privateboolisDisplayedType=false;

   publicvoidUpdate(IObservableobj){

      //这里存在一个向下转换(由继承体系中高级别的类向低级别的类转

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 经济学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1