使用 JavaScript 的观察者设计模式






4.97/5 (109投票s)
2006 年 4 月 26 日
5分钟阅读

422538

1245
确保对象之间存在清晰的边界,从而实现更大的重用性和系统可维护性。
引言
任何长时间使用 JavaScript 的人都应该熟悉创建自定义 JavaScript 对象。不熟悉 JavaScript 中 OOP 的人可以在这里阅读关于它的简短介绍。本文将介绍使用 JavaScript 实现观察者模式。
JavaScript 简要介绍
JavaScript 是一种基于原型的脚本语言(最初名为 LiveScript),其语法大致基于 C,由 Netscape Communications 开发,用于 Navigator 浏览器。与 C 一样,该语言本身没有输入或输出构造。C 依赖于标准 I/O 库,而 JavaScript 引擎则依赖于嵌入它的宿主环境。这种脚本语言容纳了其他语言可能称为过程、子例程和函数的结构,所有这些都以一种结构:自定义函数。基于 Web 的 JavaScript 的一个主要用途是与网页的文档对象模型 (DOM) 交互,以执行仅使用 HTML 无法完成的任务。JScript 是微软等同于 Netscape 的 JavaScript,用于微软的 Internet Explorer。
在 JavaScript 中创建自定义对象
创建一个新的 JavaScript 对象包括两个步骤。首先,你需要创建一个函数,其名称将是新类的名称。此函数通常称为 **构造函数**。其次,你必须使用 `new` 运算符后跟对象名称和它所需的任何参数来创建一个对象的实例。以下代码定义了一个 `Person` 函数,然后使用 `new` 运算符创建了一个 `Person` 对象
function Person( name, surname )
{
this.name = name;
this.surname = surname;
}
var salvo = new Person( ‘Salvatore’, ‘Vetro’ );
`this` 指向当前正在使用的对象实例,从而允许你向对象添加和修改属性。
如何向对象添加方法?
在 JavaScript 中,可以通过调用构造函数创建的每个对象都有一个关联的原型属性。添加新方法的语法是
customeObject.prototype.newMethodName = function;
// Specific case
Person.prototype.Speak = function(){...}
如果向对象的原型添加方法,那么使用该对象构造函数创建的所有对象都将拥有该新方法。请注意,`prototype` 本身就是一个对象,可以通过对象字面量语法为其分配属性和方法
function NewObject()
{
alert("I am a new object.");
}
NewObject.prototype =
{
alert1: function(str){alert(str);}, //new method
name: 'As you want', //new property
alert2: function(){alert("Bye.");} //new method
};
var newObject = new NewObject();
newObject.alert1("Ciao");
newObject.name;
newObject.alert2();
每次脚本尝试读取或写入对象的属性时,JavaScript 都会按照特定顺序查找属性名称的匹配项。顺序如下
- 如果属性在当前(本地)对象上有一个已赋值的值,则使用该值。
- 如果没有本地值,请检查对象构造函数的属性原语值。
- 继续沿着原型链向上搜索,直到找到属性的匹配项(并为其赋值)或搜索到达本机 `Object` 对象。因此,如果你更改构造函数的 `prototype` 属性的值,并且不覆盖构造函数实例中的属性值,JavaScript 将返回构造函数 `prototype` 属性的当前值。
观察者设计模式类图
观察者设计模式定义了主题对象和任意数量的观察者对象之间的一种 **一对多** 依赖关系,这样,当主题对象改变状态时,它的所有观察者对象都会自动得到通知和更新。
- 参与者
- 主题
- 知晓其观察者。任意数量的观察者对象可以观察一个主题。
- 提供一个用于附加和分离观察者对象的接口。
- 观察者
- 定义一个用于更新应通知主题对象变化的对象的接口。
- 具体主题
- 存储具体观察者对象感兴趣的状态。
- 当其状态改变时,向其观察者发送通知。
- 具体观察者
- 维护一个指向具体主题对象的引用。
- 存储应与其主题保持一致的状态。
- 实现观察者更新接口,以保持其状态与主题一致。
- 主题
- 协作
- 当发生可能导致其观察者状态与其自身不一致的变化时,具体主题会通知其观察者。
- 在被告知具体主题发生变化后,具体观察者对象可能会向主题查询信息。具体观察者使用此信息来协调其状态与主题的状态。
观察者设计模式序列图
以下交互图说明了主题和两个观察者之间的协作
发起更改请求的观察者对象会推迟其更新,直到它收到主题的通知。`Notify` 并非总是由主题调用。它可以由观察者或完全由另一种对象调用。
我们将要做什么?
现在你已经了解了观察者设计模式是什么以及如何使用 JavaScript 创建自定义对象。正如你在类图中所见,你必须在 Observer 类中定义两个方法(`Attach` 和 `Detach`)。为此,你需要一个集合来执行附加/分离操作。是时候编写你的第一个 JavaScript `ArrayList` 了。首先,你必须定义 `ArrayList` 应该能够做什么。
**Count** - **Add** - **GetAt** - **Clear** - **RemoveAt** - **Insert** - **IndexOf** - **LastIndexOf**。
function ArrayList()
{
this.aList = []; //initialize with an empty array
}
ArrayList.prototype.Count = function()
{
return this.aList.length;
}
ArrayList.prototype.Add = function( object )
{
//Object are placed at the end of the array
return this.aList.push( object );
}
ArrayList.prototype.GetAt = function( index ) //Index must be a number
{
if( index > -1 && index < this.aList.length )
return this.aList[index];
else
return undefined; //Out of bound array, return undefined
}
ArrayList.prototype.Clear = function()
{
this.aList = [];
}
ArrayList.prototype.RemoveAt = function ( index ) // index must be a number
{
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:
var head = this.aList.slice( 0, 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 = this.aList.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 )
{
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;
}
做得好!现在你可以同时创建观察者设计模式的 Observer 类和 Subject 类了。
观察者设计模式的 Observer 类
你只需要定义 `Update` 方法。
function Observer()
{
this.Update = function()
{
return;
}
}
观察者设计模式的 Subject 类
好的,让我们来定义三个主要方法:`Notify`、`AddObserver` 和 `RemoveObserver`。
function Subject()
{
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` 的简单方法,你可以使用它将一个对象的所有属性和方法复制到另一个对象。
function inherits(base, extension)
{
for ( var property in base )
{
try
{
extension[property] = base[property];
}
catch( warning ){}
}
}
一个简单的实现
现在你需要实现一个客户端,以便你可以将观察者附加到具体主题。例如,你可以创建一个简单的应用程序,定义一个作为可观察对象的主复选框和一些作为观察者的复选框。当具体主题改变其自身状态时,它将通知观察者发生了什么。任何与主题解耦的观察者都将独立管理通知。
/************* Concrete Subject *************/
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);
附带示例
|
在这种特定情况下,任何观察者都以相同的方式管理通知。当具体主题通知观察者新状态时,任何观察者都将其自身状态更改为使用可观察状态作为其新值。 |
协作
本文可以改进。如有任何建议,请联系我。