一、起因
在看《Ajax in action》的时候,看到它在介绍Adapter和Facade两种模式。由于目前Web开发的特色,特别是客户端Js脚本的开发,需要面对很多的变化和跨平台的挑战,所以,如果应用Adapter和Facade模式,将会非常有益于提高我们软件的可维护性,以及降低总体开发成本。
二、什么是Adapter和Facade模式
1、Adapter模式
1.1、定义:
The Adapter Pattern converts the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
1.2、解释:
Adapter模式所要解决的问题,就是接口不一致的问题。在实际的应用程序中,有的时候客户端(这里指调用方)想要调用的接口与实际上服务端(这里指被调用方)所提供的接口不一致。出现这种情况,我们可能会有两种选择,一种是修改调用方或者被调用方的接口,使之互相适应。另一种就是在调用方和被调用方之间加入一个Adapter,用其连接调用方和被调用方。
在Adapter模式里,Adapter所起的作用,就是一个接口适配器。一个Adapter类会实现(implements)调用方所期待的接口,并且在类中通过委派(delegate)来调用被调用方,从而实现两种不同接口的连接。
1.3、分类:
Adapter模式分为两种实现方式,一种是对象适配器(Object Adapters)和类适配器(Class Adapters)。其中对象适配器(Object Adapters)通过组合(composition)实现,而类适配器(Class Adapters)通过多继承实现。
1.4、关键点:
模式的关键点在于其意图。Adapter模式的意图很明显,就是为了使两个彼此不兼容的接口兼容,使一个本来并不是调用方所期待的接口看起来跟所期待的一样。
在《Head.First.Design.Patterns》这本书里,有一句话可以非常完美并且有趣的描述Adapter模式:
If it walks like a duck and quacks like a duck, then it might be a turkey wrapped with a duck adapter...
2、Facade模式
2.1、定义:
The Facade Pattern provides a unified interface to a set of interfaces in subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
2.2、解释:
Facade模式的意图也是非常明显。有时候,我们的客户端(调用方)所调用的子系统(subsystem,被调用方)过于复杂。通常,调用方需要连续调用被调用方的N个接口才能完成某种特定的任务。每次调用方重复调用操作都非常繁琐,容易出错,所以本着DRY(Don’t Repeat Yourself)的原则,我们需要提炼出一些经常用到的操作组合成一个接口。这样,每次调用方执行该功能时,仅需要调用该Facade接口,便可以轻松执行某项特定任务。
2.3、作用
Facade模式的作用主要有两点:
1、为一个太复杂的子系统(subsystem)提供一个统一的、简单的接口共调用方使用。通常需要DRY的接口都是使用率非常高,并且比较复杂的接口。将该接口提取出来,有益于简化工作,并且统一接口名称。
2、降低系统的耦合度。一批事物的变化率远大于一个事物。应用Facade模式的情景,一般是由于子系统太复杂。当调用方需要执行某些任务时,需要执行一连串操作。而随着子系统的升级,这一连串操作的变化的可能性是非常大的。但是,不管子系统如何变,这一连串操作所提供的功能或者是意义永远不变。所以,Facade模式有利于降低耦合度。
2.4、关键点:
Facade模式的关键点在于,引入Facade模式主要是为了简化和统一接口。Facade模式的接口一般是子系统的一个“快捷方式”。调用方如果还有其他的复杂功能的话,依然可以直接调用子系统的其他接口。
3、Adapter和Facade模式的区别
模式的区别就在于,要领会其根本的意图。这里有一句话,描述了Adapter和Facade模式以及Decorator模式(这里可以先不管decorator模式)。
An adapter wraps an object to change its interface, a decorator wraps an object to add new behaviors and responsibilities, and a facade "wraps" a set of objects to simplify.
三、Web开发所面临的问题
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/webkaifa/)Ajax时代的Web开发,有一个共同的特点。由于此时的浏览器端应用再也不是传统的简单页面,而变成了复杂的javascript客户端应用程序。所以,随着代码量的上升,要求我们用更加合乎软工的方式去看待Ajax应用程序。如果在此时能够引入各种OO原则及模式,将会提高Ajax应用程序的整体质量。
目前在浏览器端进行javascript开发时,主要会面临这么两种问题:
1、目前的Web开发还比较混乱。各种浏览器对W3C标准的支持都有问题,并不统一。进行Web开发时,尽管我们一直提倡基于Web标准进行开发,但有时我们往往有些力不从心。
2、Web开发过程中所使用的各种浏览器API,其本身组合的也并不足够合理。有时候为了执行某项操作,我们需要连续操作很多接口。而实际上,在大多数情况下,我们都没必要这么DRY(Do Repeat Yourself).
你可以看到,其实上面两种情况就分别对应于设计模式中的Adapter和Façade两种模式的需求。那么,下面让我们看看如何在实际的代码中应用上述两种模式解决问题?
四、如何应用Adapter和Facade模式?
在《Ajax in action》中,同样有一段代码,体现了这样的需求。我把它修改了一下,大家看看,能否从中找出Adapter和Facade模式的影子?
CGIAJAX.util["STATICS"] = {};
CGIAJAX.util.STATICS.READY_STATE_UNINITIALIZED=0;//constant
CGIAJAX.util.STATICS.READY_STATE_LOADING=1;
CGIAJAX.util.STATICS.READY_STATE_LOADED=2;
CGIAJAX.util.STATICS.READY_STATE_INTERACTIVE=3;
CGIAJAX.util.STATICS.READY_STATE_COMPLETE=4;
CGIAJAX.util.ContentLoader = function(url, onload, method, msgBody, onerror)//constructor, contains all attributes
{
this.url=url;
this.onload=onload;
this.method = (method) ? method : "GET";//default value of method is GET
this.msgBody = (msgBody) ? msgBody : null;//default value of message body send to server
this.onerror=(onerror) ? onerror : this.defaultError;//default method to handle error
this.req=null;
}
CGIAJAX.util.ContentLoader.prototype=
{
loadXMLDoc:function()//send request to server to get response
{
if (window.XMLHttpRequest)
{
this.req=new XMLHttpRequest();
}
else if (window.ActiveXObject)
{
this.req=new ActiveXObject("Microsoft.XMLHTTP");
}
if (this.req)
{
try
{
var loader=this;
this.req.onreadystatechange=function()
{
loader.onReadyState.call(loader);
}
if ("GET" == this.method)
{
this.req.open('GET',this.url,true);
this.req.send(null);
}
else
{
if (!this.msgBody)
{
this.onerror.call(this);
}
this.req.open(this.method,this.url,true);
this.req.send(this.msgBody);
}
}
catch (err)
{
this.onerror.call(this);
}
}
},
onReadyState:function()
{
var req=this.req;
var ready=req.readyState;
if (ready==CGIAJAX.util.STATICS.READY_STATE_COMPLETE)
{
var httpStatus=req.status;
if (httpStatus==200 || httpStatus==0)
{
this.onload.call(this);
}
else
{
this.onerror.call(this);
}
}
},
defaultError:function()//default error handler
{
alert("error fetching data!"
"readyState:" this.req.readyState
"status: " this.req.status
"headers: " this.req.getAllResponseHeaders());
}
};
怎么样,看到了吗?没有?那好,让我来解释一下这段代码如何应用Adapter和Facade模式的。当然,这里并不分析具体代码的含义了。
1、Adapter模式
在Ajax应用中经常用到的XHR(xmlHttpRequest)对象,就是需要Adapter模式的一个很好的例子。
XHR对象非W3C标准,所以,尽管现有的较新的浏览器都支持XHR对象,但其具体实现是不一致的。在微软的IE里,XHR是以“ActiveXObject”的样式实现的。而在mozilla浏览器里,其又以一种build-in对象的形式实现。天知道在其他的什么浏览器或者日后的日子里,这种实现方式会不会发生变化。
而对于我们常用的应用程序来说,我们并不关心这些所有的细节。我们所需要知道的,就是当我们希望创建一个XHR对象时,有一个XHR对象会被创建,并且供我们使用。至于究竟如何创建,我们对它并不关心。所以,你可以看到,在上面的代码里,我们是通过在ContentLoader类的loadXMLDoc的方法中实现的。
if (window.XMLHttpRequest)
{
this.req=new XMLHttpRequest();
}
else if (window.ActiveXObject)
{
this.req=new ActiveXObject("Microsoft.XMLHTTP");
}
这里的一个IF语句帮助我们实现了跨平台性,它便是一个Adapter。在这里,我们通过一点来自动适应各种平台的变化。我们的程序代码期待一个统一的创建XHR的接口。这段代码实现了这个接口,并且通过委托(delegate)的机制,自动帮我们用各种方法在各种不同的平台下实例化一个XHR对象。
想象一下,在不远的未来,有一种Xbrower出现,并且非常红火,值得我们兼容。我们只要改变我们的Adapter,就可以适应该变化。而我们其他的客户端代码都不需要修改,岂不很好吗?
2、Facade模式
当你需要用XHR对象向服务器请求数据的时候,你总是会很痛苦的执行一连串操作,仅仅是为了请求一次数据:
1、建立XHR对象
2、注册callback函数
3、用open方法设置请求方式,地址,模式
4、用send发送请求
5、监视请求的状态,当达到某一特定状态时,执行某项特殊功能。。。
天,好复杂,可是为什么我们要不断地重复自己呢?
上面的ContentLoader类就可以很好的屏蔽这些复杂性。当你想要从服务器端获得一些数据的时候,你关心的是服务器端的数据,而不是这整个复杂的过程。通过应用上面的这个ContentLoader类,你可以很轻松的用两行代码就获得数据。
var myRequest = new CGIAJAX.util.ContentLoader(url, refreshTalbe);
myRequest.loadXMLDoc();
可以看到,实际上ContentLoader类就是一个简单的接口,它将整个复杂的接口简单化、统一化。如此一来,我们就可以DRY了。当然,ContentLoader类并不会使我们的灵活性有所降低。如果你需要的话,你还是可以直接调用浏览器所提供的XHR接口的。可是大多数时候,我们只需要用ContentLoader类就可以很好的完成任务了。
再想象一下。假如说未来W3C将XHR作为标准,并且扩充了XHR接口的功能,那么我们怎么办?很可能扩充功能的代价就是接口的变化或者接口调用顺序的变化。如果我们没有应用Facade模式,我们会怎样?逐个修改代码中每一个用到XHR的地方?知道一切运转起来看似没有问题?或者我们现在就应用Facade模式,到时候,我们就可以轻松的修改一下Facade模式所涉及的接口内容,然后跑去看NBA了,不是吗?
五、警惕Ajax开发!
为何要警惕Ajax开发?
众所周知,由于Ajax导致Web开发模式上的变化,将会导致客户端代码的激增,由量变变成质变。而且最重要的是,Ajax现在所用到的各种技术,很大一部分都还没有成为标准,或者刚刚成为标准。这就意味着,这些东西会在未来的一段时间内,频繁变化。如果我们应用程序设计时,对该方面的变化有所准备,那么到头来痛苦的只有我们自己。
在Ajax开发中应用OO模式以及OO原则,最重要的并不是炫耀某种技术,而是期望这种已被证明的强大技术能够为我们的开发带来根本上的好处。