翻译:观察者模式—使用JavaScript实现

原文出处:http://www.codeproject.com/KB/scripting/Observer_Pattern_JS.aspx

介绍

    任何一个曾经使用过JavaScript的人都应该了解创建自定义对象的过程。不熟悉在JavaScript 中用面向对象方法(OOP)来创建对象的读者将在这里读到关于这方面的简要的介绍。本篇文章主要介绍 观察者模式 JavaScript中的实现。

JavaScript的简单介绍

    JavaScript是一种基于原型(prototype)的脚本语言(以前叫LiveScript)。它的语法松散,类似于C语言。该脚本语言是由Netscape社团开发的,用于Navigator浏览器。和C语言一样,JavaScript本身没有构造函数和析构函数。C语言依赖于标准输入/输出库;而JavaScript则依赖于执行它的宿主环境。这种脚本语言使用自定义函数,其他语言中可能会被称为过程、例程或功能。基于webJavaScript主要用于在web页面中和DOM(即文档对象模型)进行交互,以便完成一些仅使用HTML无法实现的功能。JScript是微软推出的和 JavaScript 对应的一种脚本语言,它用于微软的IE浏览器当中。

JavaScript中创建自定义对象

    创建一个新的JavaScript对象需要2个步骤。首先,你需要创建一个函数,函数名就是新建的类的名称。这个函数也就是我们经常说的 构造函数。然后,你必须使用 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()
{
    //方法体
}
Person.prototype.Speak = function() {};

    如果你在一个对象的prototype属性上添加一个方法,那么,所有通过该对象的构造函数创建的实例都具有这个新方法。注意,prototype 本身也是一个对象,而且能够通过 对象文字语法(object literal syntax 来为它定义属性和方法:


function NewObject()
{
    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

function ArrayList()
{
    
//初始化空数组
    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方法:

function Observer()
{
    
this.Update = function()
    {
       
return;
    }
}


观察者模式中的
主题类

    好了,让我们定义三个主要的方法:NofifyAddObserverRemoveObserver

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
       {
           extention[property] 
= base[property];
       }
       
catch (e)
       {
           
//Exception Handle
       }
    }
}


一个简单的实现

    现在你需要实现客户端功能以便能够附加“观察者”到相应的“主题”上。例如,你可以创建一个简单的应用程序,在应用程序中定义一个主checkbox,作为被观察对象,然后,再定义另一些checkbox作为观察者。当 主题 对象自身的状态发生变化的时候,它将通知所有依附它的 观察者。在 主题 对象中各自独立的 观察者 将独自处理这个消息。

/************* 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);


附:程序实例

    在特定情况下,所有的 观察者 对象都使用同样的方式来处理(主题对象的)消息。当对应的 主题 对象通知这些观察者有关它的“新状态”的时候,任何一个观察者都会改变自己的状态,并把这个可被观察的状态作为它的新的值。

点击此处下载示例代码

posted @ 2008-11-05 09:59  Selfocus  阅读(1146)  评论(8编辑  收藏  举报