观察者模式的应用非常广泛,项目中用到的register/unregister(订阅模式)、attach/detach,本质上都是观察者模式。
使用该模式的目的,是为了更好的实现某个实例的动作自动联动其他实例的动作。
使用时,分清楚观察者和被观察者,很重要。
【举栗子】
假设,我是孩子王,带着几个小屁孩。我要“邪恶”地“作威作福”,于是给他们分派任务!
【任务】小崽子们帮我盯着热水器,温度到60度,就提醒我洗澡;温度到100度,就提醒我沏茶。
【被观察者】热水器,它的功能就是让水温不断升高。
【观察者】
小明关注热水器的温度,一旦到了60度, 就喊:大哥哥,可以洗澡了!
小红关注热水器的温度,一旦到了100度,就喊:大哥哥,可以沏茶了!
本质上,热水器只要温度变了,就要通知到观察者小明和小红,小明和小红收到通知就做相关的动作(吆喝我!)。
如何实现呢?直接把模板抽象出来:定义两个结构体(和类很类似):
struct observable{ //被观察者的抽象。
addObs; //添加观察者的方法。
rmObs; //删除观察者的方法。
notify; //通知各个观察者的方法。
list_obs; //保存各个观察者的链表,小明和小红就在这里。
//一旦有事件(温度变化),就依次通知各个观察者。
}
struct observer{ //观察者的抽象
update; //通知各个观察者的方法。
}
接下来,定义几个真正的被观察者和观察者:
struct waterheater{ //热水器。
observable;
temperature; //具体实例的特性,热水器,有个特性叫温度。
set_temprature; //烧水,使得温度升高。
}
struct wash{ //这是小明。
observable; //继承了基类。
//可以加点其他特性,这里不需要,就不加了。
}
struct drink{ //这是小红
observable; //继承了基类
//可以加点其他特性,这里不需要,就不加了。
}
这就弄好了,开始测试了:
// 模拟个热水器出来,把热水器设置温度的方法给到热水器。
heater = malloc waterheater;
// 模拟个小明出来, 把小明的动作“喊我洗澡”添加到小明的update方法中。
ming = malloc wash;
// 模拟个小红出来, 把小红的动作“喊我沏茶”添加到小明的update方法中。
hong = malloc drink;
// 把小明和小红添加到热水器的通知链表里:
heater->addObs(ming);
heater->addObs(hong);
// 开始烧水了:
heater->set_temprature(40); // 会通知小明和小红,条件不满足,小明小红不做动作。
heater->set_temprature(50); // 会通知小明和小红,条件不满足,小明小红不做动作。
heater->set_temprature(60); // 会通知小明和小红,小明执行动作,喊我洗澡。
heater->set_temprature(90); // 会通知小明和小红,小明执行动作,喊我洗澡。
heater->set_temprature(100); // 会通知小明和小红,小明执行动作,喊我洗澡;小红执行动作,喊我沏茶。
//可以有更多动作哟
【MORE】
观察者和被观察者,未必在本地,也可以分处于不同网络节点上。
比如,热水器就是server,小红小明就是各个client,小红小明分别可以向热水器注册(register),之后,热水器有动作就会分别向小红小明发送网络信息。
小红小明有专门的线程,收到消息就根据条件执行动作。
其他类似的栗子还有很多,都可以放到火上烤一烤。比如:
服务器关机前会通知到所有终端,终端做些善后动作,比如保存数据到Database。
一旦QQ在异地登录,就短信通知给owner。
交通灯变灯就通知给行人或者车辆。
网络接口被shut,通知给其他各个应用。
再说下应用场景:一个实例,它的某些变化,其他实例需要跟随,那用观察者模式通常比较合适。
具体的代码,放在这里吧,后续会更新到github上。
/* listen.c */
#include "../adt/dlist.h"
#ifndef __LISTEN_H__
#define __LISTEN_H__
// 关于ADT 偏移的操作:
#define offsetof(type,field) ((char *) &((type *) 0)->field - (char *) 0)
#define container_of(ptr, type, member) (type*)((char*)ptr - offsetof(type, member))
// 先以typedef声明,再定义结构体,可以解决顺序倒挂问题:
// 比如StructA中包含B,但B这个函数定义又引用了StructA。
typedef struct observable_ observable_t;
typedef struct observer_ observer_t;
// 被观察者需要实现的方法:添加/删除观察者、通知各个观察者
typedef void (add_obs_t)(observable_t *self, observer_t *obs);
typedef void (rm_obs_t) (observer_t *obs);
typedef void (notify_obs_t) (observable_t *self, observer_t *obs);
// 观察者需要实现的方法:一般是上层传入的CB。
typedef void (update_t) (observable_t *self, observer_t *obs);
// 观察者的数据结构
typedef struct observer_ {
// tips:使用struct而不是指针,这样它和类一起malloc和free.
// 同时,因为线性连续,可以通过container_of直接拿到大结构体。
AdtDList_t hook;
update_t *update;
} observer_t;
// 被观察者的数据结构
typedef struct observable_ {
AdtDList_t observer_list;
add_obs_t *add_obs;
rm_obs_t *rm_obs;
notify_obs_t *notify_obs;
} observable_t;
void add_observer(observable_t *self, observer_t *observer);
void rm_observer(observer_t *observer);
void notify_observers(observable_t *self, observer_t *observer);
void observable_init(observable_t * obs);
void observer_init(observer_t * obs, update_t *cb);
#endif
/* listen.c */
#include "listen.h"
#include <stdio.h>
// 初始化被观察者
// 目的是初始化被观察者的链表(存储各个观察者)、方法(添加删除观察者以及通知观察者)
void observable_init(observable_t * obs)
{
if (obs) {
obs->add_obs = add_observer;
obs->rm_obs = rm_observer;
obs->notify_obs = notify_observers;
ADT_DLIST_HEAD_INIT(&obs->observer_list);
}
}
// 初始化观察者
// 目的是初始化观察者的update方法,以及用以被挂载的hook
void observer_init(observer_t * obs, update_t *cb)
{
if (obs) {
obs->update = cb;
ADT_DLIST_NODE_INIT(&obs->hook);
}
}
// 添加观察者
void add_observer(observable_t *self, observer_t *observer)
{
if(!self) return;
if (observer) {
ADT_DLIST_LINK_NEXT(&self->observer_list, &observer->hook);
}
}
// 删除观察者
void rm_observer(observer_t *observer)
{
if (observer) {
ADT_DLIST_UNLINK(&observer->hook);
ADT_DLIST_NODE_INIT(&observer->hook);
}
}
// 通知观察者
void notify_observers(observable_t *self, observer_t *observer/*UNUSED*/)
{
AdtDList_t* hook;
observer_t* obs;
if(!self) return;
if (observer) {
//PULL mode.
} else {
if(!ADT_DLIST_IS_EMPTY(&self->observer_list)) {
return;
}
ADT_DLIST_EACH_NEXT(&self->observer_list, hook) {
obs = container_of(hook, observer_t, hook);
obs->update(self, obs);
}
}
}
/* test.c */
#include "listen.h"
#include "stdlib.h"
#include "stdio.h"
typedef struct water_heater water_heater_t;
typedef void (set_temp_t)(water_heater_t *heater, int temp);
// 定义真正的实例类,集成了被观察者/观察者的基类(即struct),也可以添加自己的特性。
struct water_heater {
observable_t observable;
int index;
int temp;
set_temp_t *set_temp;
};
typedef struct washer {
observer_t observer;
int index;
} washer_t;
typedef struct drink {
observer_t observer;
int index;
} drink_t;
// 这是被观察者的特性方法
void heater_set_temp(water_heater_t *heater, int temp)
{
if (heater) {
heater->temp = temp;
// 这里连接了观察者和被观察者
heater->observable.notify_obs(&heater->observable, NULL);
}
return;
}
// 观察者被通知时,观察者需要执行的update函数:一个是wash,一个是drink
void washing(observable_t *observable, observer_t *observer)
{
water_heater_t* heater = container_of(observable, water_heater_t, observable);
if (heater->temp >= 60) {
printf("washing... tmp:%d \n", heater->temp);
}
return;
}
void drinking(observable_t *observable, observer_t *observer)
{
water_heater_t* heater = container_of(observable, water_heater_t, observable);
if (heater->temp >= 100) {
printf("drinking TEA... tmp:%d \n", heater->temp);
}
return;
}
int main()
{
// 实例化一个热水器出来
water_heater_t *heater = (water_heater_t *) malloc(sizeof(water_heater_t));
if (!heater) return -1;
// 初始化热水器的特性以及父类的初始化
heater->set_temp = heater_set_temp;
observable_init(&heater->observable);
// 实例化两个观察者,并初始化观察者的父类。
washer_t *washer = (washer_t *) malloc(sizeof(washer_t));
if (!washer) return -1;
observer_init(&washer->observer, washing);
drink_t *drink = (drink_t *) malloc(sizeof(*drink));
if (!drink) return -1;
observer_init(&drink->observer, drinking);
// 添加观察者
heater->observable.add_obs(&heater->observable, &washer->observer);
heater->observable.add_obs(&heater->observable, &drink->observer);
// 热水器开始有动作,会被自动观察
heater->set_temp(heater, 40);
heater->set_temp(heater, 60);
heater->set_temp(heater, 70);
heater->set_temp(heater, 80);
heater->set_temp(heater, 90);
heater->set_temp(heater, 100);
return 0;
}
/* dlist.h */
#ifndef ADT_DLIST_H_
#define ADT_DLIST_H_
#define ADT_DLIST_DEF(s_name) \
struct { \
struct s_name* next; \
struct s_name* prev; \
} DLIST
#define ADT_DLIST_NEXT(node) ((node)->DLIST.next)
#define ADT_DLIST_PREV(node) ((node)->DLIST.prev)
#define ADT_DLIST_HEAD_INIT(head) \
do { \
ADT_DLIST_NEXT(head) = (head); \
ADT_DLIST_PREV(head) = (head); \
} while(0)
#define ADT_DLIST_NODE_INIT(node) \
do { \
ADT_DLIST_NEXT(node) = NULL; \
ADT_DLIST_PREV(node) = NULL; \
} while(0)
#define ADT_DLIST_LINK_NEXT(link, node) \
do { \
ADT_DLIST_PREV(ADT_DLIST_NEXT(link)) = (node); \
ADT_DLIST_NEXT(node) = ADT_DLIST_NEXT(link); \
ADT_DLIST_PREV(node) = (link); \
ADT_DLIST_NEXT(link) = (node); \
} while(0)
#define ADT_DLIST_UNLINK(link) \
do { \
ADT_DLIST_PREV(ADT_DLIST_NEXT(link)) = ADT_DLIST_PREV(link); \
ADT_DLIST_NEXT(ADT_DLIST_PREV(link)) = ADT_DLIST_NEXT(link); \
ADT_DLIST_NODE_INIT(link); \
} while(0)
#define ADT_DLIST_EACH_NEXT(head, link) \
for (link = head; (link = ADT_DLIST_NEXT(link)) != (head); )
#define ADT_DLIST_EACH_NEXT_SAFE(head, link, save) \
for ((void) (link = ADT_DLIST_NEXT(head)), save = ADT_DLIST_NEXT(link) ; \
link != (head); \
(void) (link = save), (void) (save = ADT_DLIST_NEXT(link)))
#define ADT_DLIST_IS_EMPTY(head) (ADT_DLIST_NEXT(head) == head)
#define ADT_DLIST_IS_LINKED(node) (ADT_DLIST_NEXT(node) != NULL)
typedef struct AdtDList_s
{
ADT_DLIST_DEF(AdtDList_s);
} AdtDList_t;
#endif
pwater > gcc test_listen.c listen.c
pwater > ./a.out
washing... tmp:60
washing... tmp:70
washing... tmp:80
washing... tmp:90
drinking TEA... tmp:100
washing... tmp:100
pwater >