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






4.33/5 (5投票s)
观察者模式是处理对象之间一对多关系的一种极好的技术。
引言
设计模式有助于创建可扩展且灵活的应用程序。它们有助于解耦对象,并确保我们遵循面向对象设计原则,例如对象应该对修改关闭,对扩展开放。
观察者模式定义了对象之间的一对多依赖关系,以便当一个对象的状态发生变化时,其所有依赖者都会自动收到通知和更新。依赖关系是在运行时建立的;在设计时,对象之间互不了解,是解耦的。我们将源对象称为“被观察者”(observable),将依赖者称为“观察者”(observers)。
模式解析
让我们通过一个简单的AJAX应用程序来演示观察者模式。这个演示应用程序是一个简单的PHP代码,但设计理念并不局限于某种语言。这个小应用程序的范围是在服务器端温度值发生变化时,更新网页上的温度显示。工作流程很简单;网页以两秒的间隔发出AJAX请求,并通过回调函数获取更新的温度值。
服务器以摄氏度返回温度值,网页将其显示出来。假设应用程序运行良好,客户也很满意。第二天,客户希望增加一个选项,让客户也以华氏度显示温度。这是可选的,客户可以决定开启或关闭。我们该怎么办?我们应该修改回调函数,还是编写一个单独的函数来将摄氏度转换为华氏度,并处理显示逻辑,然后在回调函数中调用这些函数?无论哪种方式,父函数都需要知道在设计时应该调用哪些函数。
//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
显示
当我们点击摄氏度复选框时,它会以摄氏度显示温度,当我们点击华氏度复选框时,它会以华氏度显示温度。您可以使显示更花哨,比如温度计,并以图形的形式显示数字,例如水银柱。
脚本
//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是撰写一系列关于设计模式文章的合适场所,以便触及更多程序员。再次感谢您的阅读。