65.9K
CodeProject 正在变化。 阅读更多。
Home

Web 应用程序中的观察者模式(JavaScript):带有 AJAX 示例的演练

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (5投票s)

2010 年 12 月 2 日

CPOL

5分钟阅读

viewsIcon

33532

downloadIcon

198

观察者模式是处理对象之间一对多关系的一种极好的技术。

引言

设计模式有助于创建可扩展且灵活的应用程序。它们有助于解耦对象,并确保我们遵循面向对象设计原则,例如对象应该对修改关闭,对扩展开放。

观察者模式定义了对象之间的一对多依赖关系,以便当一个对象的状态发生变化时,其所有依赖者都会自动收到通知和更新。依赖关系是在运行时建立的;在设计时,对象之间互不了解,是解耦的。我们将源对象称为“被观察者”(observable),将依赖者称为“观察者”(observers)。

模式解析

让我们通过一个简单的AJAX应用程序来演示观察者模式。这个演示应用程序是一个简单的PHP代码,但设计理念并不局限于某种语言。这个小应用程序的范围是在服务器端温度值发生变化时,更新网页上的温度显示。工作流程很简单;网页以两秒的间隔发出AJAX请求,并通过回调函数获取更新的温度值。

workflow

服务器以摄氏度返回温度值,网页将其显示出来。假设应用程序运行良好,客户也很满意。第二天,客户希望增加一个选项,让客户也以华氏度显示温度。这是可选的,客户可以决定开启或关闭。我们该怎么办?我们应该修改回调函数,还是编写一个单独的函数来将摄氏度转换为华氏度,并处理显示逻辑,然后在回调函数中调用这些函数?无论哪种方式,父函数都需要知道在设计时应该调用哪些函数。

//pseudocode

function ajaxcallback(temperature){
      myCelsiusDisplay(temperature);
      myFarentheitDisplay(temperature);
}

function myCelsiusDisplay(temperature){
     displayLogic
}

function myFarenheitDisplay(temperature){
    call conversion function to convert celsius to farenheit
    display logic
}

在上面的代码中,AJAX回调与其他显示函数高度耦合。因此,每次您想添加显示逻辑时,都需要编辑AJAX回调函数,而该函数将不可重用,因为它需要在设计时了解其他函数。每当有新函数依赖于它时,它都需要被修改。它不能被移动到显示逻辑可能不同的另一个页面。此外,现在回调可能需要了解一个隐藏字段,比如复选框是否被选中。在大型应用程序中,这种情况可能会变得更加复杂。

我们的客户经常改变主意。您可能会想,明天他会要求显示开尔文温度。我们需要找到一个更好的解决方案。如果AJAX回调只知道在运行时调用的函数,而不是在设计时知道呢?然后我们可以让AJAX回调在运行时调用任何函数。这样,它就是解耦且可重用的。这听起来是个好主意。我们将AJAX回调称为“被观察者”(Observable),而被调用的函数称为“观察者”(Observers)。当被观察者(Observable)发生某些变化时,它会通知观察者(Observers)。

Using the Code

显示

UI

当我们点击摄氏度复选框时,它会以摄氏度显示温度,当我们点击华氏度复选框时,它会以华氏度显示温度。您可以使显示更花哨,比如温度计,并以图形的形式显示数字,例如水银柱。

脚本

//THIS PART DOESN'T WHAT FUNCTIONS TO BE CALLED, WHAT KIND OF DATA IT IS HANDLING
//IT OBSERVERS WILL DO THAT JOB. IT ONLY ACT AS A MODEL------------------------------

//Observable object which is observe by observers

observable=new Object();

//room to keep the observers
observable.observers=new Array();

//communicate with observers
observable.notify=function(message){

     for(i=0;i<this.observers.length;i++){

          //all observers must have the doWork function
          this.observers[i].doWork(message);
     }
}

//register an observer
observable.addObservers=function(observer){
      this.observers[this.observers.length]=observer;
}

//remove an observer
observable.removeObservers=function(observerName){

      for(i=0;i<=this.observers.length;i++){

         if(this.observers[i].Name=observerName){
              this.observers.splice(i,1);
         }
     }
}

//-----------------------------------------------------------

上面是Observable JavaScript对象,它有一个'notify'函数。AJAX请求回调将温度值传递给它的所有观察者。它有一个'observers'数组,观察者可以在运行时注册或删除。

//An Observer object.THE OBSERVABLE DOESN'T 
  KNOW ABOUT IT UNTILL IT REGISTERS ITSELF --------------------

function addCelsiusDisplay(checkbox){

     if(checkbox.checked==true){
            observer1=new Object();

             //an unique identity for the observer
            observer1.Name="celsiusdisplay"

           observer1.doWork= function(information){
              document.getElementById("celsius_display").value=information;
          }
          //register the observer

           observable.addObservers(observer1);

     }else{

          //remove the observer
         observable.removeObservers("celsiusdisplay");

     }
}

//-------------------------------------------------------------

此函数在运行时针对复选框的点击事件调用。如果复选框被选中,则将观察者添加到其被观察者对象中。如果未选中,则从被观察者对象的观察者列表中删除该观察者。所有这些耦合只发生在运行时;对象在设计时彼此不了解。

同样,我们可以在运行时添加任意数量的观察者,并以不同的方式显示温度值。我们将不再需要更改被观察者逻辑。被观察者的任何变化都是关键的,因为这种关系遵循一对多;改变被观察者可能会导致所有观察者的变化。我们现在是安全的;我们不会改变被观察者。下面的代码将在不更改被观察者任何内容的情况下更新华氏度显示。

//Another Observer Object---------------------------------

function addFarenheitDisplay(checkbox){

     if(checkbox.checked==true){
           observer1=new Object();

           observer1.Name="farenheitdisplay"

          observer1.doWork= function(information){

                var farenheit=information*(9/5)+32;
               document.getElementById("farenheit_display").value=farenheit;
          }

          observable.addObservers(observer1);

    }else{
          observable.removeObservers("farenheitdisplay");
    }

}

//--------------------------------------------------------

在这里,被观察者不知道它处理的是什么类型的数据。它只是传递服务器响应。所以这个模型可以与任何Web应用程序重用。这是个好消息。

下面的代码是异步请求服务器端的AJAX请求;页面不会刷新,我们的被观察者对象会保持不变。当响应到达时,它会调用我们被观察者的函数。

function getData(){

    //creates XML Http object
   var ajaxRequest=createXMLHttp();

    //make open request with unique url to avoid cache related issues
    ajaxRequest.open("GET", 
      "temperature.php?unique_request="+Math.random(), true);

    ajaxRequest.onreadystatechange=function()
    {
        if(ajaxRequest.readyState==4 &&
        ajaxRequest.status==200){
           //call the observable function which inturn notify its observers
           observable.notify(ajaxRequest.responseText);
        }
    }
    ajaxRequest.send(null); 
}

//The below code executes the ajax request
//every two seconds in javascript's multithreading mode.
//communicate server in every 2 secs
t=setInterval(getData,2000);

服务器端

<?php

if (file_exists('data.xml')) {
      $xml = simplexml_load_file('data.xml');
} else {
      exit('Failed to open data.');
}

$data=$xml->temperature;
$xml=null;
echo $data;

?>

服务器端的工作很简单。它读取一个XML文件并回显温度。

还提供了另一段服务器端代码来更新XML中的温度。

if(isset($_REQUEST["action_id"]) && $_REQUEST["action_id"]!=""){
    $data=$_REQUEST["temperature"];
    $sxe = new SimpleXMLElement('data.xml', NULL, TRUE);
    $sxe->temperature=$data;
    file_put_contents('data.xml', $sxe->asXML());
    echo "Temperature updated sucessfully";
}

if (file_exists('data.xml')) {
    $xml = simplexml_load_file('data.xml');
} else {
    xit('Failed to open data.');
}

$data=$xml->temperature;

?>

测试

现在是时候测试我们所做的工作了。解压附件的源代码。将其复制到您的Web服务器的根文件夹。使用服务器上的正确URL在浏览器中运行observer.php。勾选这些复选框;温度值将显示在文本框中。现在,在另一个浏览器选项卡中运行dataeditor.php。更新温度值。查看显示页面(observer.php),它现在已更新。

就是这样。现在您可以探索代码,并在您的应用程序中富有创意地使用这个模型。感谢您阅读本文。

关注点

有数百本关于设计模式的书籍和数千篇相关文章。很少有专门针对Web开发的。尽管有许多可用资源,但在许多软件开发项目中,设计模式仍然只停留在理论层面。我认为CodeProject是撰写一系列关于设计模式文章的合适场所,以便触及更多程序员。再次感谢您的阅读。

© . All rights reserved.