翻译:观察者模式—使用JavaScript实现
介绍
任何一个曾经使用过JavaScript的人都应该了解创建自定义对象的过程。不熟悉在JavaScript 中用面向对象方法(OOP)来创建对象的读者将在这里读到关于这方面的简要的介绍。本篇文章主要介绍 观察者模式 在JavaScript中的实现。
JavaScript的简单介绍
JavaScript是一种基于原型(prototype)的脚本语言(以前叫LiveScript)。它的语法松散,类似于C语言。该脚本语言是由Netscape社团开发的,用于Navigator浏览器。和C语言一样,JavaScript本身没有构造函数和析构函数。C语言依赖于标准输入/输出库;而JavaScript则依赖于执行它的宿主环境。这种脚本语言使用自定义函数,其他语言中可能会被称为过程、例程或功能。基于web的JavaScript主要用于在web页面中和DOM(即文档对象模型)进行交互,以便完成一些仅使用HTML无法实现的功能。JScript是微软推出的和 JavaScript 对应的一种脚本语言,它用于微软的IE浏览器当中。
在JavaScript中创建自定义对象
创建一个新的JavaScript对象需要2个步骤。首先,你需要创建一个函数,函数名就是新建的类的名称。这个函数也就是我们经常说的 构造函数。然后,你必须使用 new 操作符,后面跟上对象的名称以及一些必要的参数来创建一个对象的实例。下面的代码定义了一个 Person 函数,然后使用 new 操作符创建了 Person 的实例:
{
this.name = name;
this.surname = surname;
}
var salvo = new Person('Salvatore', 'Vetro');
this关键字是指向你当前正在执行的对象的实例,因此,允许你在当前对象上添加或修改对象的属性。
如何为对象添加方法?
在JavaScript中,通过调用绑定了原型属性的 构造函数 来创建任何对象。添加新方法的语法如下:
{
//方法体
}
Person.prototype.Speak = function() {};
如果你在一个对象的prototype属性上添加一个方法,那么,所有通过该对象的构造函数创建的实例都具有这个新方法。注意,prototype 本身也是一个对象,而且能够通过 对象文字语法(object literal syntax) 来为它定义属性和方法:
{
alert("I am a new object.");
}
NewObject.prototype =
{
alert1 : function(str){alert(str);}, //新方法
name : 'As you want', //新属性
Alert2 : function(){alert('Bye.');}, //新方法
};
var newObject = new NewObject();
newObject.alert1("Ciao");
newObject.name;
newObject.alert2();
脚本每次尝试读/写对象的属性的时候,JavaScript会按照特定的顺序来搜寻和指定名称匹配的属性。顺序如下:
l 如果该属性已经分配给当前对象,则使用该属性的值;
l 如果在当前对象中没有搜索到指定的属性,则检查该对象构造函数的prototype属性的值;
l 沿着prototype链一直查找,直到找到匹配的属性(已经为它赋值),否则,一直会查找到 Object 对象。因此,如果你改变了构造函数的prototype属性的值,并且没有在构造函数的某个实例中重写属性的值,JavaScript会返回对象当前prototype属性的值。
观察者模式类图
观察者模式 定义了一个 主题对象 和若干 观察者对象之间的 “一对多”的依赖。因此,当 主题对象 改变了其状态,所有的 观察者对象 都会被通知并且自动更新。
图1 观察者模式类图
l 参与者:
u Subject(抽象主题)
Ø 能够知道它自己的观察者,若干观察者对象可能监视一个主题对象;
Ø 提供一个接口,用来附加和取消观察者对象;
u Observer(抽象观察者)
Ø 它为对象定义了一个(自我)更新的接口,并且当主题对象发生改变的时候能够被通知;
u ConcreteSubject(具体主题)
Ø 存储具体观察者感兴趣的状态;
Ø 当它的状态改变的时候,通知它所有的观察者对象;
u ConcreteObserver(具体观察者)
Ø 持有一个具体主题的引用;
Ø 存储和主题对象一致的状态,并且该能够保留该状态;
Ø 实现抽象观察者的Update接口,以便能够同主题对象的状态保持一致;
l 工作方式:
u 当某个具体主题状态发生改变的时候,通知它的所有观察者对象,以便保证这些观察者对象的状态同它的状态保持一致;
u 当被告知具体主题对象发生改变,一个具体观察者对象可能会去查询主题对象,以便获得一个消息;该观察者使用这个消息来使它的状态同主题对象的状态保持一致;
观察者模式序列图
下面这张交互图展示了一个主题对象和两个观察者对象之间的协作:
图2 观察者模式序列图
观察者对象初始化变化请求,它不会立即更新,直到通过主题对象的Notify方法来获得一个变化的通知。主题的Notify方法并非总是被主题对象调用,它也能够被观察者对象调用,或者完全被其他不同类型的对象调用。
接下来我们将做什么
现在你已经知道了什么是 观察者模式,并且也了解了如何使用JavaScript来创建自己的对象。正如你在图1中看到的一样,你必须在 主题类 中定义2个方法(Attach 和 Detach)。为了达到这个目的,你需要一个 集合 来完成 Attach/Detach 方法。现在是时候写你的第一个JavaScript ArrayList 对象了。在定义 ArrayList 对象之前,你必须知道 ArrayList 能够完成一些什么样的功能。
ArrayList功能列表:
1、Count
2、Add
3、GetAt
4、Clear
5、RemoveAt
6、Insert
7、IndexOf
8、LastIndexOf
{
//初始化空数组
this.aList = [];
}
ArrayList.prototype.Count = function()
{
return this.aList.length;
}
ArrayList.prototype.Add = function(object)
{
//把新添加的对象放在数组的最后
return this.aList.push(object);
}
ArrayList.prototype.GetAt = function(index) //index必须是整数
{
if (index > -1 && index < this.aList.length)
{
return this.aList[index];
}
else
{
return undefined; //超出了数组范围,返回undefined
}
}
ArrayList.prototype.clear = function()
{
this.aList = [];
}
ArrayList.prototype.RemoveAt = function(index)
{
var m_count = this.aList.length;
if (m_count > 0 && index > -1 && index < this.aList.length)
{
switch (index)
{
case 0:
//移除数组中的第一个元素
this.aList.shift();
break;
case m_count - 1:
//移除数组中最后一个元素
this.aList.pop();
break;
default:
//获取前面index个元素,生成一个新数组
var head = this.aList.slice(0, index);
//获取index之后的元素,生成一个新数组
var tail = this.aList.slice(index + 1);
//组合两个子数组
this.aList = head.concat(tail);
break;
}
}
}
ArrayList.prototype.Insert = function(object, index)
{
var m_count = this.aList.length;
var m_returnValue = -1;
if (index > -1 && index <= m_count)
{
switch (index)
{
case 0:
this.aList.unshift(object);
m_returnValue = 0;
break;
case m_count:
this.aList.push(object);
m_returnValue = m_count;
break;
default:
var head = this.aList.slice(0, index - 1);
var tail = this.aList.slice(index);
this.aList = header.concat(tail.unshift(object));
m_returnValue = index;
break;
}
}
return m_returnValue;
}
ArrayList.prototype.IndexOf = function(object, startIndex)
{
var m_count = this.aList.length;
var m_returnValue = -1;
if (startIndex > -1 && startIndex < m_count)
{
var i = startIndex;
while (i < m_count)
{
//循环遍历数组,直到找到和参数object相同的元素
if (this.aList[i] == object)
{
m_returnValue = i;
break;
}
i++;
}
}
return m_returnValue;
}
ArrayList.prototype.LastIndexOf = function(object, startIndex)
{
var m_count = this.aList.length;
var m_returnValue = -1;
if (startIndex > -1 && startIndex < m_count)
{
var i = m_count - 1;
while (i >= startIndex)
{
if (this.aList[i] == object)
{
m_returnValue = i;
break;
}
i--;
}
}
return m_returnValue;
}
非常不错!你现在可以创建观察者模式中的 观察者类 和 主题类 了。
观察者模式中的 观察者类
你只需要定义Update方法:
{
this.Update = function()
{
return;
}
}
观察者模式中的 主题类
好了,让我们定义三个主要的方法:Nofify、AddObserver和RemoveObserver:
{
this.observers = new ArrayList();
}
Subject.prototype.Notify = function(context)
{
var m_count = this.observers.Count;
for (var i = 0; i < m_count; i++)
{
this.observers.GetAt(i).Update(context);
}
}
Subject.prototype.AddObserver = function(observer)
{
if (!observer.Update)
{
throw 'Wrong parameter';
}
this.observers.Add(observer);
}
Subject.prototype.RemoveObserver = function(observer)
{
if (!observer.Update)
{
throw 'Wrong parameter';
}
this.observers.RemoveAt(this.observers.IndexOf(observer, 0));
}
JavaScript中的继承
在JavaScript中有许多方法来模拟继承。一个非常简单的方式是定义一个名称为 Inherits 的方法,在该方法中,你可以复制一个对象的所有属性和方法到另一个对象中。
{
for (var property in base)
{
try
{
extention[property] = base[property];
}
catch (e)
{
//Exception Handle
}
}
}
一个简单的实现
现在你需要实现客户端功能以便能够附加“观察者”到相应的“主题”上。例如,你可以创建一个简单的应用程序,在应用程序中定义一个主checkbox,作为被观察对象,然后,再定义另一些checkbox作为观察者。当 主题 对象自身的状态发生变化的时候,它将通知所有依附它的 观察者。在 主题 对象中各自独立的 观察者 将独自处理这个消息。
var mainCheck = document.createElement("input");
mainCheck.type = 'checkbox';
mainCheck.id = 'MainCheck';
Inherits(new Subject(), mainCheck);
mainCheck["onclick"] = new Function("mainCheck.Notify(mainCheck.checked)");
/**************** Observer ****************/
var obsCheck1 = document.createElement("input");
var obsCheck2 = document.createElement("input");
obsCheck1.type = 'checkbox';
obsCheck1.id = 'Obs1';
obsCheck2.type = 'checkbox';
obsCheck2.id = 'Obs2';
Inherits(new Observer(), obsCheck1);
Inherits(new Observer(), obsCheck2);
obsCheck1.Update = function(value)
{
this.checked = value;
}
obsCheck2.Update = function(value)
{
this.checked = value;
//Add
}
mainCheck.AddObserver(obsCheck1);
mainCheck.AddObserver(obsCheck2);
附:程序实例
在特定情况下,所有的 观察者 对象都使用同样的方式来处理(主题对象的)消息。当对应的 主题 对象通知这些观察者有关它的“新状态”的时候,任何一个观察者都会改变自己的状态,并把这个可被观察的状态作为它的新的值。