`
frank-liu
  • 浏览: 1661510 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

再回首Observer Pattern

 
阅读更多

早些时候曾经写过一篇关于observer pattern的文章,当时是基于.net平台的event特性理解做了一点解读。时过境迁,一下又过去三年多了。再回过头来看这些东西的时候,感觉当初的说法还是过于教条和书面化,内容的深度还是不够。

一个想法

如果不是从一种严格的教科书定义来看observer pattern的话,我们可以从一些示例中引申出这种思想来。我们可以从一个报纸订阅发布的这个示例来考虑这个问题。假设我们有一个报纸的发行出版商,每经过一个阶段就会发行新的报纸或者杂志。这个时候我们就需要通知一些感兴趣的用户或者已经预订的用户。通知的形式可以是发邮件给用户,或者是寄送到用户的指定地址或者短信通知等等。

总的来说,就是我出版商这一方产生了一个有新报纸出版这个事件的时候,我们就需要通知相关的人。

有一种很笼统的思想如下,就是既然我有新报纸发行了,那就一个一个通知呗,这种思路的典型代码如下:

 

 

// 假定我们要提供一些比如期刊号或者推荐专栏之类的信息
public void notifyUsers()
{
    String journalNumer = getJournalNumber();
    String promotedColumn = getPromotedColumn();
    userA.sendEmail(journalNumer, promotedColumn);
    userB.deliverNewspaper(journalNumer, promotedColumn);
    userC.sendSMS(journalNumer, promotedColumn);
}

这种思路对应的类关系图比较直接简单,如下图:

 

我们如果仔细考虑上面的代码,就会发现其中存在如下的一些问题:

1. 我们是在通知的代码里直接对一个一个的用户对象来发送消息通知。这就有变动的可能。比如说以后如果有新加入的用户需要我们通知或者有的用户不需要了,我们就要在这部分代码里频繁的改动。

2. 由于要通知多个用户,如果每个用户被通知的方式都不一样,就没办法用一种统一的方式来处理,也不好提取出公共的地方。

总的来说,前面提到的代码我们就面临着两个变化,一个是接收消息变化通知的用户会变;另外就是每个用户接收通知的具体形式也不一样,也会变动。

报纸订阅的初步思路

结合前面提出的两个问题,我们来作进一步的思考。前面第一个问题是订阅用户的频繁变化。实际上,我们在这部分代码里更希望的就是针对所有已经登记注册的用户发送通知,具体哪些用户登记和取消不是我发送通知的方法应该关注的。而且,既然有用户可以加入和退出,我们就应该有一个可以允许用户登记和取消的功能。这样,我们就应该有registerObserver()和removeObserver()两个方法。为了保留所有登记的用户,我们可以简单的用一个ArrayList来保存。

这样,我们这两个方法就可能有如下方法的一种实现:

 

// observers 表示所有已经注册用户的列表,ArrayList

public void registerObserver(Observer o)
{
    observers.add(o);
}


public void removeObserver(Observer o)
{
      int i = observers.indexOf(o);
      if(i >= 0)
        observers.remove(i);
}

 看上去这部分代码是可行的,但是慢着。我们还忽略了一个简单的细节。既然我们要在registerObser和removeObserver方法里面来添加和移除用户,根据传进来的Observer参数,这些用户要么就必须是Observer类型或者是Observer类型的子类。这就正好也引入到我们前面碰到的第二个问题。

    第二个问题就在于我们通知的所有用户的方式不一样,那么我们是否可以针对向用户的通知提取出一个什么公共或者抽象的东西呢?假定我们向所有用户推送的基本是相同的信息,比如说报刊期号、报刊推荐专栏,那么我们可以提供一个统一的接口方法update()。每个不同的用户针对update作不同的实现,比如发邮件的就发邮件,发短信的发短信。

按照前面的讨论,我们的类图可以设计成如下:

 

在前面的设计中,我们就已经用到了封装变化以及面向抽象而不是具体类这两个面向对象的设计原则。

进一步的改进

再来看上面的设计,我们会发现,这其中还有一些可以进一步改进的地方。我们看类Publisher,其中包含有方法registerObserver, removeObserver这两个专门管理登记用户的方法。除了这两个方法以外,还包含一些比如获取发布信息,报纸刊号等功能的方法。再结合我们前面的面向抽象的思路想想,有两个理由,我们可以考虑作一点修改。一个就是如果后面还需要有一个新的类似的出版物,比如说这个出版机构要发行一种新的杂志,它也要采用类似的方式来通知用户的话,同样也需要有一个维护注册用户的方法。既然这些部分的功能几乎都是重复的,我们是否可以考虑将这部分的功能单独提取出来作为一个公共的父类或者接口呢?这样每个具体的子类只要更多关注自身的业务相关逻辑。可以说,兼顾到了面向抽象,也更加职责明确了。还有一个理由就是,如果我们需要做测试的话,面向具体的类反而被限死了,针对接口或者抽象类的话会更灵活一些。我们可以将以上的设计进一步改进成如下图:

这类图就是observer pattern的一个典型结构。

 

在java class library中的应用

在java本身的类库里有好几个地方是对observer pattern的实现。一个就是java.util.Observer和java.util.Observable.在这里java定义了一个Observer的接口,通过统一实现里面的update方法来实现对事件的相应。而Observable是一个具体的类。这里定义了添加和移除observer的方法,并实现了通知Observer的方法。不过由于是一个具体的类,对于某个要定义通知事件的类来说,显得稍微有点不够灵活。尤其是当某个要定义某个通知事件的类如果已经继承了某个类了,这就不能再通过继承Observable类来定义事件。

总结

        Observer pattern主要的目的是为了解决当一个对象变化的时候,需要通知和影响到其他相关联对象的耦合问题。怎么样保证达到一个松耦合的状态,使得关联对象的增减不会影响到我本身代码的变动。该模式主要分为两个大的部分,一个会发生事情的对象端,可以称其为Observerable,它主要是来事的,是事件的根源,有什么事情要变动了,它就会通知其他感兴趣的对象。它也提供了一个加入注册和退出的方法给另外一端的对象使用。另外一部分是对发生时间感兴趣的对象端,可以称之为Observer端。这一段的对象主要是接收消息通知,然后根据自身作相应的变化。

        这篇文章讲Observer pattern主要还是想作为一个引子,来引出后续的一些东西。实际上Observer pattern的思想在一些event based application以及reactor pattern的应用中都可以找到它的影子。理解了它,对于后续那些复杂的地方就方便多了。

参考材料

Head first design patterns.

  • 大小: 55.1 KB
  • 大小: 49.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics