原文首发于我的微信公众号:GeekArtT.

Observer设计模式是为了解决“信息同步更新”的问题而存在的。它试图解决这样一个问题:如果有“一堆对象”都跟随“某一对象”的变化而变化,那么,如何能够保持“这堆对象”能够同步更新呢?特别是,“这堆对象”很可能在运行时(run-time)不断被添加或者被删除,你设计的机制该如何既能保持增添/删除的灵活性,而又能使“当前这堆对象”同步更新呢?

仅仅是抽象地看待上述这个问题,或许很难有思路。解决方案的形成可以依托于生活中具体业务场景的类比。

很多书籍介绍观察者(Observer)这个设计模式喜欢运用“内容订阅者”和“发布信息平台”的对应关系。这个比喻从生产流程上讲,完全符合,但是作为比喻,还是显得有些生硬。我认为,一个更好的、更富有启发性的类比是“品牌商店”与“旗下加盟商/分店”在价格上的依存关系。好不夸张的说,一旦做出这个类比,解决方案就会自然浮现。

例如,McDonald在一座城市开店,其价格都是高度统一。如果某一个分店的价格低于别的分店,就会打乱跨国大公司的整体战略部署,是绝不允许的。所以,一个核心的目标是,每一个分店的价格都必须时刻同步统一。另一方面,每个地区的分店,都有可能随着市场的反馈而做出调整:或者因为销量过低而关闭,或者因为某个新的商圈的出现而增加分店。那么,在分店的数目、位置都在随时变动的情形下,应该用一套什么机制才能保证分布在城市各地的分店都使用统一的价格呢?

一旦有了恰当的比喻,问题自然迎刃而解。保持分店的价格有什么困难的?

在总店的通讯本上,记录下各个分店的电话号码。一旦有了新的价格策略,立马根据通讯本上记录的电话号码,拨打各个分店的电话,通知各个分店。如果建立了新的分店,就在总店的通讯本上添加新分店的电话号码;如果关闭了一家分店,就在总店通讯本上去掉相应分店的电话号码。由此,各分店的价格便能够同步统一,且保持相对好的灵活性。

有了这段生活场景作为指导纲领,我们便可以写出具体的Observer设计模式的代码了。考虑到GoF(Design Patterns)这本书的巨大影响,我首先展示如何运用上述类比一步步写出GoF中Observer设计模式这一节的示例代码。

在GoF中,示例代码的应用场景是:如果我有一个class能够实时获取当前时间,但是,我又想用两种方式Digital Clock和Analog Clock的方式去做展示(可以想象为`10:28`和`10h28min`这样不同的展示方式)。

首先设计“分店”类。它最主要的功能就是根据“总店”通报的信息,将自己的信息更新。对应GoF的C++代码,Subject类充当了“总店”的角色。

class Subject;

class Observer {
public:
virtual ~Observer();
virtual void Update(Subject* theChangedSubject) = 0;
protected:
Observer();
}

这里Update()方法是需要有Subject类作为参数的。因为,作为分店,自己是不可以任意地改动价格的,你有且只能根据“总店”的消息去做变更,别的数据源都是禁止的。

然后设计“总店”类,用它来衍生出“能够实时获取当前时间的类”。按照之前的思路,这个类除了必要的构造/析构函数外:

  • 需要有一个“通讯本”记录各分店的电话号码。

  • 相应地,应该具备添加/删除电话号码的方法。

  • 另外,还应当有一个方法,能够根据通讯本上记录的信息拨打各个分店的电话号码。这一行为抽象地说,即是能够根据一个信息记录本,来逐一通知各个“分店”进行信息更新。

GoF中的C++的代码如下:

class Subject {
public:
virtual ~Subject(); virtual void Attach(Observer *);
virtual void Detach(Observer *);
virtual void Notify();
protected:
Subject();
private:
List<Observer*> *_observers;
};

这里,列表_observers充当了通讯本的角色,用它来保存各个“分店”的指针。本来,指针即是变量的门牌号。把“分店”的这个号码记录在通讯本上,就能够任意地访问“分店”。从这点上来说,它比仅仅记录的电话号码还要强大有用。

Attach()Detach()方法自然就是用于添加/删除这个通讯本的信息了。而Notify()即是字面意思所表述的,用于通知各个分店“价格有变”。而通知的目的,实质是让各分店做更新,所以,这里作为父类的Notify()是有统一的更新方法:

void Subject::Notify() {
ListIterator<Observer*> i(_observers); for(i.First(); !i.IsDone(); i.Next()) {
i.CurrentItem()->Update(this);
}
}

这里所用的方法,即是依次将通讯本上的分店调取出来,让它们各自调用自己的Update()方法,并将自己,this,作为参数传入。告知分店把自己作为信息源来进行同步更新。

构造完父类,下面可以构建具体的时钟类。首先是能够实时获取时间的“总店”类:

class ClockTimer : public Subject {
public:
ClockTimer(); virtual int GetHour();
virtual int GetMinute();
virtual int GetSecond(); void Tick();
} void ClockTimer::Tick() {
// update internal time-keeping state
// ...
Notify();
}

这里,Tick()方法用于定时抓取最新的时间状态信息。

然后是第一个“分店”类:

class DigitalClock: public Widget, public Observer {
private:
ClockTimer* _subject;
public:
DigitalClock(ClockTimer*);
virtual ~DigitalClock(); virtual void Update(Subject*);
// overrides Observer operation virtual void Draw();
// overrides Widget operation;
// defines how to draw the digital clock
}; DigitalClock::DigitalClock(ClockTimer* s) {
_subject = s;
_subject->Attach(this);
} DigitalClock::~DigitalClock() {
_subject->Detach(this);
}

这个类里面,运用了private变量_subject去保存它的数据源,并在构造函数中将其初始化。之后便可以运用这个_subject去核对Update是否是被正确的数据源通知,以此来作为更新与否的标准:

void DigitalClock::Update(Subject* theChangedSubject) {
if(theChangedSubject == _subject) {
Draw();
}
} void DigitalClock::Draw() {
// get the new values from the subject
int hour = _subject->GetHour();
int minute = _subject->GetMinute();
// etc. // draw the digital clock
}

之后是第二个“分店”AnalogClock的构建:

class AnalogClock : public Widget, public Observer {
private:
ClockTimer* _subject;
public:
AnalogClock(ClockTimer*);
virtual ~AnalogClock(); virtual void Update(Subject*);
virtual void Draw();
// ...
}; // ...

构建完了这三大部分,便可以直接使用上面完成的Observer模式了:

ClockTimer* timer = new ClockTimer;

AnalogClock* analogClock = new AnalogClock(timer);
DigitalClock* digitalClock = new DigitalClock(timer);

这样,每当timer中的Tick()方法抓取到了新的时间信息,两个Clock就会得到及时更新,并作出相应地不同展示。


如果你喜欢我的文章或分享,请长按下面的二维码关注我的微信公众号,谢谢!

设计模式之“Observer”注疏#01的更多相关文章

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

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

  2. [置顶] head first 设计模式之----Observer pattern

    浅谈设计模式之----观察者模式      观察者模式也是我们日常程序编写中碰到比较多的一种设计模式.首先,所谓观察者模式定义就是指:在对象之间定义了一对多的依赖,这样一来,当一个对象的状态发生变化的 ...

  3. 面向对象设计模式——观察者(OBSERVER)模式

    定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.  Observer模式描述了如何建立这种关系.这一模式中的关键对象是目标(subject ...

  4. 设计模式--观察者模式Observer(对象行为型)

    一.观察者模式 观察者模式是在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新.观察者模式也被称之为:主题-观察者模式,发布-订阅模式,前者是一,后者是多. ...

  5. 设计模式之Observer(观察者)(转)

    Java深入到一定程度,就不可避免的碰到设计模式(design pattern)这一概念,了解设计模式,将使自己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广泛,遵循 ...

  6. 设计模式-观察者模式(Observer Pattern)

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

  7. 大话设计模式--观察者模式 Observer -- C++ 实现实例

    大话设计模式--1.观察者模式: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有的 观察者对象,使他们能够自动更新自己. 使用场合: 当一 ...

  8. 设计模式之observer and visitor

    很长时间一直对observer(观察者)与visitor(访问者)有些分不清晰. 今天有时间进行一下梳理: 1.observer模式 这基本就是一个通知模式,当被观察者发生改变时,通知所有监听此变化的 ...

  9. [工作中的设计模式]观察者模式observer

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

随机推荐

  1. CSS - DOM 经常使用方法

    offset() 方法返回或设置匹配元素相对于文档的偏移(位置). 包括两个属性值:top,left. position() 方法返回匹配元素相对于父元素的位置(偏移). 包括两个属性值:top,le ...

  2. WinFrom中使用WPF的窗体

    步骤 1.添加WindowsFormsIntegration.dll .System.Windows.Forms.和System.Xaml,PresentationCore.PresentationF ...

  3. C# const和readonly修饰符的区别

    const 的概念就是一个包含不能修改的值的变量.常数表达式是在编译时可被完全计算的表达式.因此不能从一个变量中提取的值来初始化常量.如果 const int a = b+1;b是一个变量,显然不能再 ...

  4. sqrt()平方根计算函数的实现2——牛顿迭代法

    牛顿迭代法: 牛顿迭代法又称为牛顿-拉夫逊方法,它是牛顿在17世纪提出的一种在实数域和复数域上近似求解方程的方法.多数方程不存在求根公式,因此求精确根非常困难,甚至不可能,从而寻找方程的近似根就显得特 ...

  5. MVC5 DB FIRST

    跟着师父一直在做codefirst的开发,最近有个新需求,就是需要人家的数据库,然后来开发,现在出现问题了.整理如下 目前有个现成的我们之前的codefirst的工程代码,我记得师父说过,根据数据库生 ...

  6. oracle学习 笔记(2)

    题记:在使用Oracle数据库的时候,发现Oracle是没有自动增长列来实现主键的,所以在此记录学习.(PS:如果哪里有错误或者不足的地方还请大家帮忙指出来) 二.序列(自动增长列) 为此问题博主也是 ...

  7. 配置uwsgi

    首先要明确的是,如果你喜欢用命令行的方式(如shell)敲命令,那可以省去任何配置. 但是,绝大多数人,还是不愿意记那么长的命令,反复敲的.所以uwsgi里,就给大家提供了多种配置,省去你启动时候,需 ...

  8. nginx+lua安装配置

    1.选定源码目录选定目录 /usr/local/ cd /usr/local/ 2.安装PCRE库cd /usr/local/wget ftp://ftp.csx.cam.ac.uk/pub/soft ...

  9. JavaScript对象原型写法区别

        体现对象原型分步式写法 //原型分步式写法 //构造函数 function Person(){} //对象原型 Person.prototype.name = 'Avensatr'; Pers ...

  10. HTTP协议(三)

    一.首先我们画一个图来看一下HTTP协议: 难道方法只有POST GET吗?NO,还有一些少用的方法. 二.请求方法有哪些? GET POST HEADER PUT TRACE DELETE OPTI ...