在面向对象编程中,设计模式是一套被广泛认可的解决特定问题的模板。观察者模式是其中之一,它帮助解耦对象,并确保遵循面向对象的设计原则,例如对象应该对修改封闭,对扩展开放。
观察者模式定义了对象之间的一对多依赖关系,使得当一个对象状态发生变化时,所有依赖它的对象都会自动收到通知并更新。这种依赖关系是在运行时建立的;在设计时,对象之间并不相互了解,是解耦的。称源对象为“可观察的”,依赖它的对象为“观察者”。
让通过一个简单的AJAX应用程序来了解观察者模式。这个演示应用程序是一个简单的PHP代码,但设计概念并不局限于一种语言。这个小型应用程序的目的是,每当服务器端的温度值发生变化时,更新网页上的温度显示。工作流程很简单;网页每两秒发起一次AJAX请求,并通过回调函数获取更新的温度值。
服务器以摄氏度的温度值响应,网页显示它。假设应用程序运行良好,客户也很满意。第二天,客户想要增加一个选项,让客户可以选择以华氏度显示温度。这是可选的,客户可能会决定打开或关闭。该怎么办?应该修改回调函数,还是编写一个单独的函数来将摄氏度转换为华氏度,并为显示逻辑调用这些函数?无论哪种方式,父函数都需要在设计时知道要调用哪些函数。
function ajaxcallback(temperature){
myCelsiusDisplay(temperature);
myFahrenheitDisplay(temperature);
}
function myCelsiusDisplay(temperature){
displayLogic
}
function myFahrenheitDisplay(temperature){
call conversion function to convert celsius to fahrenheit
display logic
}
在上述代码中,AJAX回调与其它显示函数高度耦合。因此,每次想要添加显示逻辑时,都需要编辑AJAX回调函数,这将不会是可重用的,因为它需要在设计时知道其他函数。它不能移动到另一个页面,其中显示逻辑可能会有所不同。此外,现在回调可能需要知道一个隐藏字段,比如复选框是否被选中。在更大的应用程序中,这种情况可能会变得更加复杂。
客户经常改变主意。可能会想知道,当他第二天要求以开尔文显示温度时,会怎么做。需要找到一个更好的解决方案。AJAX回调只知道在运行时调用的函数,而不是在设计时。这样,它就可以在任何页面上解耦并重用。这听起来是个好主意。称AJAX回调为“可观察的”,要调用的函数为“观察者”。可观察的将在发生某些变化时通知观察者。
当点击摄氏度复选框时,它会以摄氏度显示温度,当点击华氏度复选框时,它会以华氏度显示温度。可以使显示更加花哨,比如温度计,并以图形形式显示数字,比如水银水平。
PHP:
// Observable object which is observed by observers
observable = new Object();
observable.observers = new Array();
observable.notify = function(message){
for(i=0; i
以上是可观察的JavaScript对象,它有一个'notify'函数。AJAX请求回调将温度值传递给所有观察者。它有'observers'数组,这是观察者将在运行时注册或移除的地方。
PHP:
function addCelsiusDisplay(checkbox){
if(checkbox.checked == true){
observer1 = new Object();
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");
}
}
这个函数在复选框点击事件上运行。如果复选框被选中,则将其添加到可观察对象的观察者列表中。如果未选中,则从观察者列表中移除观察者。所有这些耦合只在运行时发生;对象在设计时不知道彼此。
同样,可以在运行时添加任意数量的观察者,并以不同的方式显示温度值。不再需要更改可观察逻辑。任何对可观察的更改都是关键的,因为这种关系遵循一对多;更改可观察的可能会导致所有观察者发生变化。现在是安全的;不会更改可观察的。下面的代码将在不更改任何内容的情况下更新华氏度显示。
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(){
var ajaxRequest = createXMLHttp();
ajaxRequest.open("GET", "temperature.php?unique_request=" + Math.random(), true);
ajaxRequest.onreadystatechange = function(){
if(ajaxRequest.readyState == 4 && ajaxRequest.status == 200){
observable.notify(ajaxRequest.responseText);
}
}
ajaxRequest.send(null);
}
下面的代码每两秒执行一次AJAX请求,在JavaScript的多线程模式下与服务器通信。
var 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 successfully";
}
测试
现在是测试所做的事情的时候了。解压缩附带的源代码。将其复制到Web服务器的根文件夹。使用服务器的正确URL在浏览器中运行observer.php。检查那些复选框;温度值将显示在文本框中。现在在另一个浏览器标签中运行dataeditor.php。更新温度值。查看显示页面(observer.php),它现在会更新。