原文首发于我的微信公众号: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. HTML+CSS-采用DIV+CSS布局的利弊

    单纯的TABLE表格,在浏览器运行上面来说,其效率不如DIV+CSS方式快速,当然,这样也就会影响用的体验. 而且,针对于相同的HTML页面来说TABLE布局,不利于搜索引擎的爬虫爬行下载页面.从而造 ...

  2. MySQL相关信息(二)

    1.修改MySQL提示符 (1)连接客户端时通过参数指定 shell>mysql -u root -p --prompt  提示符 C:\Users\Administrator>mysql ...

  3. Python自动生产表情包

    作为一个数据分析师,应该信奉一句话--"一图胜千言".不过这里要说的并不是数据可视化,而是一款全民向的产品形态--表情包!!!! 表情包不仅仅是一种符号,更是一种文化--是促进社交 ...

  4. 【转】nginx配置:location配置方法及实例详解

    location匹配的是nginx的哪个变量? $request_uri location的匹配种类有哪些? 格式 location [ 空格 | = | ~ | ~* | !~ | !~* ] /u ...

  5. Linux+Nginx+Asp.net Core部署

    上篇<Docker基础入门及示例>文章介绍了Docker部署,以及相关.net core 的打包示例.这篇文章我将以oss.offical.site站点为例,主要介绍下在linux机器下完 ...

  6. Win10 UWP开发系列:开发一个自定义控件——带数字徽章的AppBarButton

    最近有个项目有一个这样的需求,在文章浏览页底部有几个AppBarButton,其中有一个是评论按钮,需要在评论按钮上显示一个红色数字,类似微信的新消息提醒: 这种设计在iOS和Android平台都是很 ...

  7. cuda事件的使用

    cudaEvent_t start,stop; cudaEventCreate(&start);//创建事件 cudaEventCreate(&stop); cudaEventReco ...

  8. lvm的vg扩容

    本次扩容的目的是要扩展 / 的整体容量,具体操作如下: 1.首先查看是否存在未分配的磁盘 [root@NH-Test-44 ~]# fdisk -l Disk /dev/vda: 53.7 GB, 5 ...

  9. 解决vagrant up启动失败,停留在Booting VM...过程的方法

    如图,这种情况是因为没有正确关闭虚拟机导致的,关闭时应在命令行使用vagrant halt 命令,如果直接关机便会出现这种情况. 解决办法:直接启动VirtualBox,并在命令行vagrant ha ...

  10. php Redis常用命令

    redis是一个很好的缓存工具,下面我们就来介绍一下他怎么使用 启动 Redis 服务src/redis-server或者src/redis-server redis.conf src/Redis-s ...