1.介绍
装饰者模式能够在不改变对象自身的基础上,在程序运行期间对对象动态的添加职责。与继承相比,装饰者是一种更加轻便灵活的做法,这是一种“即用即付”的方式。
2.实例引入
<span style="font-size:14px;"> var Plane = function(){};
Plane.prototype.fire = function(){
console.log( '发射普通子弹' );
}
var MissileDecorator = function( plane ){
this.plane = plane;
}
MissileDecorator.prototype.fire = function(){
this.plane.fire();
console.log( '发射导弹' );
}
var AtomDecorator = function( plane ){
this.plane = plane;
}
AtomDecorator.prototype.fire = function(){
this.plane.fire();
console.log( '发射原子弹' );
}
var plane = new Plane();
plane = new MissileDecorator( plane );
plane = new AtomDecorator( plane );
plane.fire();</span>
这种给对象以一条链的方式进行引用,形成了一个聚合对象,这些对象都拥有相同的接口(fire)方法,当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的下一个对象。
改写对象或者对象的某个方法,并不需要使用“类”来装饰实现装饰者模式:
var plane = {
fire: function(){
console.log( '发射普通子弹' );
}
}
var missileDecorator = function(){
console.log( '发射导弹' );
}
var atomDecorator = function(){
console.log( '发射原子弹' );
}
var fire1 = plane.fire;
plane.fire = function(){
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function(){
fire2();
atomDecorator();
}
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
下面引入一个AOP装饰函数,其中把职责分为更细的函数,随后通过装饰者把他们合并到一起。
<html>
<body>
用户名:<input id="username" type="text"/>
密码: <input id="password" type="password"/>
<input id="submitBtn" type="button" value="提交"></button>
</body>
<script>
var username = document.getElementById( 'username' ),
password = document.getElementById( 'password' ),
submitBtn = document.getElementById( 'submitBtn' );
var formSubmit = function(){
if ( username.value === '' ){
return alert ( '用户名不能为空' );
}
if ( password.value === '' ){
return alert ( '密码不能为空' );
}
var param = {
username: username.value,
password: password.value
}
ajax( 'http:// xxx.com/login', param ); // ajax 具体实现略
}
submitBtn.onclick = function(){
formSubmit();
}
var validata = function(){
if ( username.value === '' ){
alert ( '用户名不能为空' );
return false;
}
if ( password.value === '' ){
alert ( '密码不能为空' );
return false;
}
}
var formSubmit = function(){
if ( validata() === false ){ // 校验未通过
return;
}
var param = {
username: username.value,
password: password.value
}
ajax( 'http:// xxx.com/login', param );
}
submitBtn.onclick = function(){
formSubmit();
}
Function.prototype.before = function( beforefn ){
var __self = this;
return function(){
if ( beforefn.apply( this, arguments ) === false ){
// beforefn 返回false 的情况直接return,不再执行后面的原函数
return;
}
return __self.apply( this, arguments );
}
}
var validata = function(){
if ( username.value === '' ){
alert ( '用户名不能为空' );
return false;
}
if ( password.value === '' ){
alert ( '密码不能为空' );
return false;
}
}
var formSubmit = function(){
var param = {
username: username.value,
password: password.value
}
ajax( 'http:// xxx.com/login', param );
}
formSubmit = formSubmit.before( validata );
submitBtn.onclick = function(){
formSubmit();
}
</script>
</html>
这段代码验证和输入完全分离开来,他们不再有任何偶和关系。其中formSubmit 函数除了提交ajax请求以外,还要验证用户输入的合法性;分离校验输入和提交ajax请求的代码,把输入的逻辑放到validata函数中,并且约定validate函数返回false的时候表示验证未通过。
总结
装饰者模式和代理模式的结构看起来非常像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,他们实际上都保留了对另外一个对象的引用,并且对那个对象发送请求。代理模式强调Proxy与它的实体之间的关系,这种关系一开始就被确定,可以静态的表达。而装饰者模式用于一开始不能确定对象的全部功能时,代理模式通常只有一层代理-本体的引用,而装饰者模式通常会形成一条长长的装饰链。比如在代理图片预加载的例子中,本体负责设置img节点的src,代理则提供预加载的功能,这看起来也是“加入行动”的一种方式;而装饰者模式是实实在在的为对象增加新的职责和行为,而代理做的事情还是和本体一样,最终都是设置src。