观察者模式(有时又被称为发布/订阅模式)是软件设计模式的一种。
在这种模式中,一个目标对象管理所有依赖于它的观察者对象,并且在自身状态发生改变时主动通知这些观察者。这通常通过调用各观察者所提供的方法来实现。观察者模式通常也被用来实现事件处理系统。
——维基百科
概念本身比较难理解,所以我试图通过一个现实中的例子来说明这个观察者模式。
我们知道,世界上最伟大的爱,莫过于母爱。母亲关心你的一切:
你吃饱了吗?穿暖了吗?找到女朋友了吗?
如果把天下的母亲归为一类,儿女归为一类,我们可以理解为:
母亲时刻关注着儿女的变化,从观察者模式的角度来讲,母亲是观察者,观察着儿女们的一举一动和各种需求。
而儿女则是被观察者,他们发生的变化和需求都可以通知到母亲。
那么,我们就来做这样一个例子。
PHP中的观察者模式
<?php
// 母亲类接口,包括所有关心你的人
interface M{
// 获取儿女的信息
function update($msg);
}
// 儿女类接口,包括所有被关心的人
interface C{
// 添加观察者
function addObserver(M $obj);
// 删除观察者
function delObserver(M $obj);
// 通知观察者
function noteMsg($msg);
}
class Mother implements M{
// 存放所有子女告诉她的话
private $childtoldtome = array();
// 接收子女的信息
public function update($msg){
$this->childtoldtome[] = $msg;
}
// 告诉其他人子女刚才说了什么
public function telleveryone(){
echo "我儿子刚才跟我说:\"",
$this->childtoldtome[count($this->childtoldtome)-1],
"!\"<br/>";
}
}
class Child implements C{
public $name;
public $age;
public $sex;
private $hasfriend;
private $observer = array();
public function __construct(
$name,
$age,
$sex,
$hasfriend = 0
){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
$this->hasfriend = $hasfriend;
}
// 添加观察者
public function addObserver(M $ob){
if(!in_array($ob, $this->observer)){
$this->observer[] = $ob;
}
}
// 删除观察者
public function delObserver(M $ob){
if(in_array($ob, $this->observer)){
$key = array_search($ob, $this->observer);
unset($this->observer[$key]);
}
}
// 通知观察者
public function noteMsg($msg){
foreach($this->observer as $allobj){
$allobj->update($msg);
}
}
// 找女朋友
public function getgrilfriend($gname){
$this->hasfriend = 1;
$this->noteMsg(
"妈,我有女朋友了!她叫".$gname
);
}
// 涨工资
public function upwage($pay){
$this->noteMsg(
"妈,我涨工资了!涨了".$pay
);
}
}
// 实例化一个母亲对象:小明妈
$xiaomingM = new Mother();
// 假设小明妈有两个儿子,小明和小李
$xiaoming = new Child("xiaoming", 20, "Male");
$xiaoli = new Child("xiaoli", 23, "Male");
// 给小明和小李分别添加监听者
$xiaoming->addObserver($xiaomingM);
$xiaoli->addObserver($xiaomingM);
// 小明找到女朋友了,告诉他妈
$xiaoming->getgrilfriend("小芳");
$xiaomingM->telleveryone();
// 小李涨工资了,告诉他妈
$xiaoli->upwage(1000);
$xiaomingM->telleveryone();
?>
在上面的例子中,共有两个主要的类,分别实现了两个接口。Mother 类有两个方法:
第一个方法 update() 用来接受儿子的状态;
第二个方法 telleveryone() 用来告诉其他人儿子刚才跟她说了什么。
Child 类则有添加观察者、删除观察者、通知观察者、找女朋友和涨工资等方法。
当调用:
getgrilfriend()
或者:
upwage()
的时候,都会自动调用:
noteMsg()
再由 noteMsg() 通知所有观察者:
$allobj->update($msg);
这就是一个比较典型的观察者模式。
上面的例子中,面向对象的封装、继承和多态也都用到了。
那么,在 JavaScript 中,这个观察者模式又应该怎么实现呢?
JavaScript中的观察者模式
下面来看 JavaScript 中的实现。
function Mother(){
var childtoldtome = new Array();
this.update = function(msg){
childtoldtome.push(msg);
}
this.telleveryone = function(){
document.write(
"我儿子刚才跟我说:\"" +
childtoldtome[childtoldtome.length - 1] +
"!\"<br/>"
);
}
}
function Child(name, age, sex){
this.name = name;
this.age = age;
this.sex = sex;
var hasfriend = 0;
var observer = new Array();
// 增加观察者
// 由于 JavaScript 中没有 PHP 那样的参数类型声明,
// 这里需要判断传入对象是否属于 Mother
this.addObserver = function(ob){
if(ob instanceof Mother){
observer.push(ob);
}else{
throw "Type error, arguments must be an instance of Mother";
}
}
// 删除观察者
this.delObserver = function(ob){
if(ob instanceof Mother){
var key = -1;
for(var i = 0; i < observer.length; i++){
if(observer[i] === ob){
key = i;
break;
}
}
if(key !== -1){
observer.splice(key, 1);
}
}else{
throw "Type error, arguments must be an instance of Mother";
}
}
// 通知观察者
this.noteMsg = function(msg){
for(var i = 0; i < observer.length; i++){
observer[i].update(msg);
}
}
// 找女朋友
this.getgrilfriend = function(gname){
hasfriend = 1;
this.noteMsg(
"妈,我有女朋友了!她叫" + gname
);
}
// 涨工资
this.upwage = function(pay){
this.noteMsg(
"妈,我涨工资了!涨了" + pay
);
}
}
// 实例化母亲对象
var xiaomingM = new Mother();
// 小明妈有两个儿子
var xiaoming = new Child(
"xiaoming",
20,
"Male"
);
var xiaoli = new Child(
"xiaoli",
23,
"Male"
);
// 给小明和小李添加观察者
xiaoming.addObserver(xiaomingM);
xiaoli.addObserver(xiaomingM);
// 小明找到女朋友,告诉他妈
xiaoming.getgrilfriend("小芳");
xiaomingM.telleveryone();
// 小李涨工资了,告诉他妈
xiaoli.upwage(1000);
xiaomingM.telleveryone();在上面的例子中,同样有 Mother 和 Child 两个类。
Mother 类有两个主要方法 update() 和telleveryone()
其中 update() 用来接收儿子的信息,telleveryone() 用来告诉其他人儿子刚才说了什么。
Child 类则有:
addObserver()
delObserver()
noteMsg()
getgrilfriend()
upwage()
当涨工资和找女朋友的方法被调用时:
getgrilfriend()
↓
noteMsg()
↓
通知观察者
↓
Mother.update()
以及:
upwage()
↓
noteMsg()
↓
通知观察者
↓
Mother.update()
这样,母亲就能够在儿女状态发生变化的时候及时得到通知。
PHP和JavaScript的区别
从上面的例子可以看出,虽然 JavaScript 同样可以实现观察者模式,但是两者之间还是有一些区别。
第一,JavaScript 和 PHP 的继承方式并不完全一样,所以没有必要像 PHP 一样再定义 M 和 C 这样的接口。
第二,在这个例子中,PHP 可以直接通过:
addObserver(M $ob)
限制传入参数的类型。
而 JavaScript 没有这样的参数类型声明,所以需要自己判断:
if(ob instanceof Mother){
...
}通过这种方式判断传入的对象是否是 Mother 的实例。
还能怎样改进?
上面的两个例子在特定情况下都还可以继续改进。
可以看到,每个实例对象中都有一些相同的方法。
如果实例化的对象数量很多,那么这些相同的方法会分别存在于每个对象中,占用更多内存。
在 PHP 中,可以把这些公共方法做成静态方法;而在 JavaScript 中,则可以考虑把这些公共方法放到对象的 prototype 上。
这样,同样的方法就不需要在每个实例中重复保存,相同的信息只需要保留一份,可以节省一些系统资源。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。