数字营销 · Web开发 · 基础设施

PHP和JavaScript的观察者模式对比

通过“母亲和儿子”的例子理解观察者模式,并对比 PHP 与 JavaScript 中观察者模式的实现方式。

观察者模式(有时又被称为发布/订阅模式)是软件设计模式的一种。

在这种模式中,一个目标对象管理所有依赖于它的观察者对象,并且在自身状态发生改变时主动通知这些观察者。这通常通过调用各观察者所提供的方法来实现。观察者模式通常也被用来实现事件处理系统。

——维基百科

概念本身比较难理解,所以我试图通过一个现实中的例子来说明这个观察者模式。

我们知道,世界上最伟大的爱,莫过于母爱。母亲关心你的一切:

你吃饱了吗?穿暖了吗?找到女朋友了吗?

如果把天下的母亲归为一类,儿女归为一类,我们可以理解为:

母亲时刻关注着儿女的变化,从观察者模式的角度来讲,母亲是观察者,观察着儿女们的一举一动和各种需求。

而儿女则是被观察者,他们发生的变化和需求都可以通知到母亲。

那么,我们就来做这样一个例子。

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();

在上面的例子中,同样有 MotherChild 两个类。

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 一样再定义 MC 这样的接口。

第二,在这个例子中,PHP 可以直接通过:

addObserver(M $ob)

限制传入参数的类型。

而 JavaScript 没有这样的参数类型声明,所以需要自己判断:

if(ob instanceof Mother){
...
}

通过这种方式判断传入的对象是否是 Mother 的实例。

还能怎样改进?

上面的两个例子在特定情况下都还可以继续改进。

可以看到,每个实例对象中都有一些相同的方法。

如果实例化的对象数量很多,那么这些相同的方法会分别存在于每个对象中,占用更多内存。

在 PHP 中,可以把这些公共方法做成静态方法;而在 JavaScript 中,则可以考虑把这些公共方法放到对象的 prototype 上。

这样,同样的方法就不需要在每个实例中重复保存,相同的信息只需要保留一份,可以节省一些系统资源。

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。