你的位置:首页 > ASP.net教程

[ASP.net教程]设计模式(二):自己动手使用“观察者模式”实现通知机制


在之前发布Objective-C系列博客的时候,其中提到过OC的通知机制,请参考《Objective-C中的老板是这样发通知的(Notification)》这篇博客。在之前关于Notification的博客中,只介绍了Foundation框架中的通知的使用方式。正如前面博客中提到的那样,通知是“一对多的关系”,类似于广播。一个人发通知,多个人接收。这也就是设计模式中的“观察者模式”。接收者的一方是Observer(观察者),而发送方是Subject(主题)。一个人要想成为Observer,要在Subject中进行注册,也就是说要给Subject说,我要成为你的观察者,然后Subject就会给Observer推送消息。

我们不仅要知其然,还要知其所以然。今天博客的主题是“观察者模式”(Observe Pattern),所以我们要先通过一个小的Demo来理解一下“观察者模式” ,当然使用的是Swift语言来实现的(语言只是载体呢,主要还是模式不是)。通过一个小Demo对“观察者模式”进行学习后,紧接着会看一下在Swift中是如何使用Foundation框架中的通知的,并给出相应的示例。最后就是我们放大招的时候了,我们会参照着Foundation框架中的通知机制来实现我们自己的“通知中心”,说白了,就是我们不用Foundation的通知机制,我们自己写,但是使用方式与Foundation框架中的通知机制几乎相同。这应该就是Foundation框架中通知机制的实现原理吧。在本博文的开头需要有个干货预警呢。

 

一、认识“观察者模式”(Observe Pattern)

1.观察者模式的定义

开门见山,先来看一下观察者模式的定义吧:

观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。

上面就是观察者模式的定义。也许你看定义有些抽象,其实观察者模式并不难理解。举个栗子🌰,比如老板在一个办公室里开会,办公室里有部分员工,在办公室的员工就是Observer(观察者),正在开会的老板就是Subject(主题:负责发送通知---Post Notification)。如果其他员工也想成为Observer,那么必须得进入(addObserver)正在开会的会议室成为观察者。员工成功观察者后收到通知得做一些事情吧(doSomething),比如记个笔记神马的。如果此时员工闹情绪,不想听老板开会了,于是通过removeObserver走出了会议室。上面这个过程其实就是观察者模式。

 

2.从一个示例来认识“观察者模式”

上面描述了发通知的老板和接收通知的员工的观察者模式。接下来我们要用一个完整的示例来描述这个通知的过程,从一个完整的示例中来观察一下“观察者模式”的运作方式。当然场景还是使用Boss发送通知,员工接收通知的场景。这显然就是一对多的关系。

了解设计模式怎么会没有“类图”呢,当然在本篇博客以及本系列博客中使用的“类图”并不是真正的类图,只是看起来像类图,也就是类"类图"。但是类“类图”足以表示类间的各种关系。下方就是我们将要实现的“类图”。当然下方的的结构有很大的重构空间的,下方的基类完全可以使用protocol来实现的,但是为了简化结构我们用了简单的继承。但是下方示例是完全可以来表示“观察者模式”的。因为今天我们的主题是“设计模式”,其他关于重构的问题我们先不予理会。

下方SubjectType的基类就是通知者基类,负责发通知的,其中有表示发布消息的info: String字段,以及保存多个观察者的observerArray的数组(因为Subject :Observers 是1 对多的关系,我们在这儿使用数组类存储Observers)。在SubjectType类中还有三个方法,简单的说就是注册观察者(registerObserver)、移除观察者(removeObserver)、通知观察者(notifyObserver)这三个方法。Boss是SubjectType的子类,继承了SubjectType的所有属性以及要重写SubjectType中的三个方法,来完整要做的事情。在Boss中还有setInfo()方法,负责在更新Info信息的时候调用发出通知的方法。

ObserverType是观察者的基类,其中的info:String字段来存储接收到的通知信息。udpate()方法就是接收到通知后要执行的方法,也就是说Boss一发通知,员工就会执行update()方法,而其中的display()方法就是对上述信息进行输出。当然把SubjectType以及ObserverType做成基类,不利于我们后期的扩展或者在后期扩展中会产生重复的代码,使用使用接口或者结合者其他的设计模式可以很好的解决该问题。不过这不在于今天我们这篇博客的讨论范围之内,我们今天的重点是“观察者模式”。

    

3.上述“类图”的具体实现

原理在上述类“类图”中说的很明白了,接下来我们要通过上面的介绍来开始编写我们的代码,去实现上述“观察者模式”。上面的Boss负责发通知,Coder和PM负责监听Boss发的通知。下方就是我们的具体实现。

(1)ObserverType与SubjectType基类的实现如下图所示,这两个基类中的内容与上述“类图”中的描述一致。在SubjectType基类中的observesArray中存储的是ObserverType类型(包括其子类)的对象,也就是所有的观察者。

  

(2)下方就是我们负责发通知的大Boss。Boss继承自SubjectType,当Boss执行setInfo()方法时(也就是修改info的值时)就会调用notifyObservers()进行通知的发送。在Boss中的registerObserver()方法用来添加监听者(为了防止重复添加,我们在添加前先进行移除),removeObserver()则是负责移除监听者,notifyObservers()是发送通知并调用观察者相应的方法。具体实现如下所示:

    

(3)下方实现的是两个观察者,分别是Coder(程序员)和PM(产品经理)。这两者都是ObserverType基类的子类,重写了ObserverType的update()和display()方法。观察者在观察到Subject的info被改变后,就会执行其中的update()方法。Coder和PM类的具体实现如下所示。

   

(4)经过上面这三小步,我们的Demo就实现完了,该到了我们测试的时候了,下方是测试用例以及输出结果。从输出结果我们不难看出,第一次发通知的时候,Coder和PM都接收到了通知,因为他们俩都是“观察者”。紧接着我们移除了Coder观察者,在发送第二次通知的时候,因为现在Coder不再是观察者了,所以第二次发送通知只有PM能收到。具体如下所示。

   

 

二、Foundation框架中的通知

在Foundation框架中是有一套完整的“观察者模式”机制的,也就是“通知机制”。在《Objective-C中的老板是这样发通知的(Notification)》这篇博客中就介绍了Foundation框架中的通知机制。本篇博客的第二部分就回顾一下Foundation框架中的“通知机制”,当然在本篇博客中我们会使用Swift来实现Foundation框架中的通知机制。

1. 简述NotificationCenter

在Foundation框架中的通知机制中有通知中心(NotificationCenter)这个概念,通知中心扮演者调度通知的作用。Subject往通知中心发送通知,由通知中心进行统一管理,把该Subject发送的消息分发给相应的观察者。可以这么说,通知中心是一个大集合,集合中有多个Subject和多个Observe的集合。而通知中心扮演的角色就是讲Subject与相应的Observer进行关联。下方就是简单的原理图了。

  

2.Foundation框架中的通知的使用

(1)创建Subject

Foundation中自带的通知机制使用起来比较简单的,我们暂且将发送消息的称为Subject,通知的观察者称为Observer。下方是通知的Subject的实现,下方的Boss扮演的就是Subject角色。如果Boss要发送通知的话,需要下方几部:

I. 创建消息字典,该字典承载的就是观察着说获取的信息。

II. 创建通知(NSNotification),该通知也是要发送给Observer的。通知中的信息量更大啊,其中包括发出通知的Subject的名字(每个Subject都有一个名字),还包括发送通知的对象,以及我们创建的消息字典。

III. 将该通知发送给“通知中心”----NotificationCenter,NotificationCenter会根据Notification所承载的信息来找到观察此通知的所有Observers,并把该Notification传给每个观察者。

下方就是Subject发送通知的具体做法。

   

(2)添加Observer

上面这一步是创建Subject,也就是往“通知中心”发送通知。接下来就是要往“通知中心”添加Observer,下方的代码就是往“通知中心”添加Observer。在添加Observer是,我们要指定该观察者所观察的是哪一个Subject。这也就是为什么要为Subject命名了,在添加Observer时就是通过Subject的名字来指定其观察的对象的。除了指定观察对象外,还需要指定收到通知后所执行的方法。在指定的方法中需要有一个参数,该参数就是用来接收上方Subject所发出的NSNotification的对象的。Observe的具体实现方式如下所示。

有一点需要注意的是,在当前对象释放时要移除观察者。

   

(3)测试用例

经过上面的两步,我们就已经使用Foundation框架中的通知机制将Subject和Observers进行了关联。接下来我们将对上方的代码进行测试,下方是我们的测试用例。测试用例灰常的简单了,在此就不做过多的赘述了。

   

 

三、照猫画虎:自定义通知中心

经过上面的部分,想必应该对“观察者模式”有所了解吧。经过上面的第二部分,你多Foundation中的通知机制使用是没有太大问题的。但是仅仅会使用不是我们想要的,还是那句话,要知其所以然。接下来我们就“照猫画虎,比葫芦画瓢”,自己实现一套专属自己的通知机制。在我们接下来要实现的通知机制中我们要根据Foundation框架中通知调用方式,来实现我们自己的通知。自定义通知的调用方式我们要做到与Foundation框架中的通知的使用方式一致,但是我们的命名是不同的。这部分才是今天博客的大招。

1.原理分析

我们先对Foundation框架中的通知机制进行观察,找一些灵感。当然我们看不到Foundation框架的源码,但是我们可以通过其对外暴露的接口来猜测其中通知的实现机制。下方是我们经过分析然后在经过推敲画出来的我们将要自己实现的通知机制的“类图”。我们也将根据下方的类图来实现属于我们自己的通知机制,“类图”如下。

下图中的MyCustomNotificationCenter就对应的NSNotificationCenter,  MyCustomNotification则对应着NSNotification,而下方的MyObserver类与MySubject类在Foundation中对外应该是不可见的(这是个人猜测了),这两个类是为了实现该通知机制所创建的Subject和Observer。下方“通知机制”的运作方式就是Boss将Notification发送到NotificationCenter,然后NotificationCenter在通过其内部实现机制,将Boss发送过来的Notification发送到Coder。

在MyCustomNotification这个通知载体类中(类比NSNotification)的name字段表示发送通知的对象的名称,也就是上面的“Boss”, object字段就指的是上述示例的Boss的对象,userInfo就代表着发送给Observer的信息字典。MyObserver中存储的就是观察者对象(observe)和观察者对象收到通知后要执行的方法(selector)。

MySubject类扮演者“观察者模式”中的Subject,其中的notification字段记录着要发送的通知,其类型是MyCustomNotification。MySubject类中的observers是一个数组,其中存储的是该Subject对应的所有观察者。其中还分别有添加观察者(addCoustomObserver()), 移除观察者(removeCustomObserver()), 发送通知(postNotification())方法。具体如下“类图”所示。

中间的红框中的MyCustomNotificationCenter类,就是通知中心了(类比NSNotificationCenter), 该类的对象是通过defaultCenter()方法获取的单例。在该方法中有一个名为center的字段,center字段是字典类型,该字典的Key是我们为MySubject对象指定的name, Value是MySubject对象。其中也有移除、添加观察者,发送通知等方法。

    

2、Subject与Observer的代码实现

上面的原理也扯的够多了,接下来我们要根据上面的描述来使用Swift语言进行代码实现。还是直接上代码来的直观。在实现代码之前有一点需要声明的就是,该示例不能在Playground中实现,因为在Playground中执行performSelector()方法会抛出异常,所以我们需要在真正的工程中去实现(如果想简单一些,可以创建一个控制台程序来进行测试)。

(1). MyCustomNotification(类比NSNotification)具体实现

下方代码就是MyCustomNotification的具体实现了。通过下方的具体代码不难看出,name字段表示发送通知的对象的名称,也就是上面的“Boss”, object字段就指的是上述示例的Boss的对象,userInfo就代表着发送给Observer的信息字典。该类比较简单就不做过多赘述了。

  

(2). MyObserver的具体实现

下方代码就是MyObserver类的具体实现,该类还是比较简单的。MyObserver中存储的就是观察者对象(observer)和观察者对象收到通知后要执行的方法(selector)。当收到通知时,就会执行observer的selector方法。

    

 

(3). MySubject的实现

紧接着要实现我们的MySubject类了,MySubject类将Notification与Observers进行关联。具体说来就是当MySubject收到Notification中,就会遍历其所有的观察者(observers的类型是ObserveArray,其中存储的是MyObserver的对象),遍历观察者时就会去执行该观察者所对应的selector方法。下方的notification存储的就是Subject所要发出的通知。observers字段是数组类型,其中存储的是MyObserver的对象。addCustomObserver()方法其实就是往observers数组中添加观察者,而

removeCustomObserver()方法则是移除observers数组中的观察者。postNotification()方法的功能则是对observers数组进行遍历取出MyObserver的对象,然后执行该对象中的selector方法,并且将notification作为selector方法的参数。具体实现如下所示。

   

3.“通知中心”的代码实现

上面实现的是Notification、Subject以及Observer的代码的实现,接下来要实现“通知中心”模块。因为该模块的代码比较多,业务逻辑相对复杂,所以我想把这部分代码进行拆分,然后各个击破。下方截图是MyCustomNotificationCenter类的定义,我们先将类中的代码折叠,然后将折叠的代码进行拆分各个击破。下方是通知中心MyCustomNotificationCenter类的定义方式。

   

 

(1)在MyCustomNotificationCenter类中我们也模拟NSNotificationCenter的defaultCenter()方法来获取该类的单例,具体代码如下所示。下方我们将其构造器声明为私有,防止其在外部进行实例化。然后使用静态方法defaultCenter()来返回一个当前类的静态实例,下方就是Swift中比较简单的“单例模式”了。

   

(2)、下方的的方法就是通知中心发送通知的过程了,对应着NSNotificationCenter.defaultCenter()中的postNotification(notifaction)。我们要实现postNotification()方法也有一个参数,该参数就是Subject要发送的通知。在postNotification()方法中,首先会调用getSubjectWithNotifaction(notification)方法来从center中获取可以发送该notification的Subject对象。在getSubjectWithNotifaction(notification)中,如果center中没有可以发送该notification的对象,那么就创建一个MySubject对象,并将该notification赋值给这个新建的MySubject对象,最后将我们创建的这个新的subject添加进center数组中。然后调用该subject对象的postNotification()方法即可,具体实现如下所示。

   

(3)下方代码就是添加监听着了,与NSNotificationCenter.defaultCenter()中的addObserver()方法相对应。首先我们把传入的参数生成MyObserver的对象,然后通过aName从center字典中获取相应的MySubject对象。如果center中没有对应的MySubject对象,我们就创建该对象,并且将该对象的notification属性暂且指定为nil。最后调用MySubject类中的addCustomObserver()方法进行观察者的添加。

   

(4) 下方代码就比较简单了,就是移除观察者。首先也是通过name从center字典中获取MySubject的对象,然后调用MySubject对象的removeCustomObserver()方法进行移除掉。具体代码如下所示。

   

 

4.测试用例

经过上面的艰苦跋涉,我们自己定义的通知机制终于完成了。下方就是我们为上述自定义通知机制所创建的测试用例。将下方的测试用例与Foundation框架中的通知机制的测试用例(本篇博客第二部分)相比是非常相似的。至此我们自定义的通知就Over了,这也就是Foundation框架中通知机制实现的大概原理吧,当然Foundation框架还对其做了各种优化。但是万变不离其宗,都是“观察者模式”的应用。

下方是我们自定义通知的测试用例,是在本篇博客中第二部分的代码的基础上进行修改单,就是Foundation框架中的通知进行了替换。具体如下所示:

上面是在Swift2.1版本中实现的代码,在Swift2.2中的Selector的参数有所变化,在此还是需要说明一下的,aSelector参数在Swift2.2中得使用#selector(类.方法),如下所示:

 

如果你对本篇博客的内容从头到尾的进行阅读,并且将上面的实例用自己熟悉的一门语言来实现的话,想必你对“观察者模式”更进一步的了解了吧。

同样,在本篇博客的末尾,我们给出类本篇博客是Dmeo, Github: https://github.com/lizelu/DesignPatterns-Swift