一、模拟传统面向对象语言的装饰者模式:

假设我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可以升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时可以发射导弹,升到第三级时可以发射原子弹
// 首先是原始的飞机类
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();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
这种给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(fire 方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的 下一个对象。 
因为装饰者对象和它所装饰的对象拥有一致的接口,所以它们对使用该对象的客户来说是透明的,被装饰的对象也并不需要了解它曾经被装饰过,这种透明性使得我们可以递归地嵌套任意多个装饰者对象

二、装饰者也是包装器

从功能上而言,decorator能很好地描述这个模式,但从结构上看,wrapper的说法更加贴切。装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会

三、回到JavaScript的装饰者

JavaScript 语言动态改变对象相当容易,我们可以直接改写对象或者对象的某个方法,并不需要使用“类”来实现装饰者模式
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装饰函数

Function.prototype.before = function( beforefn ){
// 保存原函数的引用
var __self = this;
// 返回包含了原函数和新函数的"代理"函数
return function(){
// 执行新函数,且保证this不被劫持,新函数接受的参数
// 也会被原封不动地传入原函数,新函数在原函数之前执行
beforefn.apply( this, arguments );
return __self.apply( this, arguments ); // 执行原函数并返回原函数的执行结果, // 并且保证this不被劫持
}
}
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
};
Function.prototype.before 接受一个函数当作参数,这个函数即为新添加的函数,它装载了新添加的功能代码。接下来把当前的 this保存起来,这个this指向原函数,然后返回一个“代理”函数,这个“代理”函数只是结构上像代理而已,并不承担代理的职责(比如控制对象的访问等)。它的工作是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序,让新添加的函数在原函数之前执行(前置装饰),这样就实现了动态装饰的效果。
// 实例1:
document.getElementById = document.getElementById.before(function() {
alert (1);
});
var button = document.getElementById('button');
console.log(button) // 实例2(改造window.onload):
window.onload = function(){
alert (11);
}
window.onload = ( window.onload || function(){} ).before(function(){
alert (22);
}).after(function(){
alert (33);
}).after(function(){
alert (44);
});
值得提到的是,上面的AOP实现是在Function.prototype上添加 before 和 after 方法,但许多人不喜欢这种污染原型的方式,那么我们可以做一些变通,把原函数和新函数都作为参数传入 before 或者 after 方法:
var before = function( fn, beforefn ){
return function(){
beforefn.apply( this, arguments );
return fn.apply( this, arguments );
}
}
var a = before( function(){
alert (3)
}, function(){
alert (2)
}
);
a = a();

五、AOP的应用实例

不论是业务代码的编写,还是在框架层面,我们都可以把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起,这有助于我们编写一个松耦合和高复用性的系统。分离业务代码和数据统计代码,无论在什么语言中,都是AOP的经典应用之一。

5.1:假设现在我们需要点击一个按钮后既要负责打开登录浮层,和负责数据上报,我们看到在showLogin函数里,这是两个层面的功能,在此处却被耦合在一个函数里。

{/* <button tag="login" id="button">点击打开登录浮层</button> */}
var showLogin = function(){
console.log( '打开登录浮层' );
log( this.getAttribute( 'tag' ) );
}
var log = function( tag ){
console.log( '上报标签为: ' + tag );
// (new Image).src = 'http:// xxx.com/report?tag=' + tag; // 真正的上报代码略
}
document.getElementById( 'button' ).onclick = showLogin; // 使用 AOP分离之后,代码如下:
var showLogin = function(){
console.log( '打开登录浮层' );
}
var log = function(){
console.log( '上报标签为: ' + this.getAttribute( 'tag' ) );
}
showLogin = showLogin.after(log); // 打开登录浮层之后上报数据
document.getElementById('button').onclick = showLogin;

5.2:插件式的表单验证

<body>
用户名:<input id="username" type="text"/>
密码: <input id="password" type="password"/>
<input id="submitBtn" type="button" value="提交">
</body>
传统的表单验证的写法:formSubmit 函数在此处承担了两个职责,除了提交 ajax 请求之外,还要验证用户输入的合法性。这种代码一来会造成函数臃肿,职责混乱,二来谈不上任何可复用性。
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 );
}
submitBtn.onclick = function(){
formSubmit();
}
改造之后的写法,在这段代码中,校验输入和提交表单的代码完全分离开来,它们不再有任何耦合关系, formSubmit = formSubmit.before( validata )这句代码,如同把校验规则动态接在 formSubmit 函数 之前,validata 成为一个即插即用的函数,它甚至可以被写成配置文件的形式,这有利于我们分 开维护这两个函数。再利用策略模式稍加改造,我们就可以把这些校验规则都写成插件的形式, 用在不同的项目当中。
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();
}

六:装饰者模式和代理模式

  装饰者模式和代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供 一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送 请求。
  代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。代理模式的目的是,当直 接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代 理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用就是为对 象动态加入行为。换句话说,代理模式强调一种关系(Proxy 与它的实体之间的关系),这种关系 可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确 定对象的全部功能时。代理模式通常只有一层代理本体的引用,而装饰者模式经常会形成一条 长长的装饰链。

JavaScript设计模式(装饰者模式)的更多相关文章

  1. javascript设计模式——装饰者模式

    前面的话 在程序开发中,许多时候都并不希望某个类天生就非常庞大,一次性包含许多职责.那么可以使用装饰者模式.装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象.本文将 ...

  2. 从ES6重新认识JavaScript设计模式: 装饰器模式

    1 什么是装饰器模式 向一个现有的对象添加新的功能,同时又不改变其结构的设计模式被称为装饰器模式(Decorator Pattern),它是作为现有的类的一个包装(Wrapper). 可以将装饰器理解 ...

  3. JavaScript设计模式----装饰者模式

    装饰者模式的定义: 装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责.与继承相比,装饰者是一种更轻便灵活的做法. 装饰者模式的特点: 可以动态的给某个对 ...

  4. 读书笔记之 - javascript 设计模式 - 装饰者模式

    本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...

  5. JavaScript设计模式—装饰器模式

    装饰器模式介绍 为对象添加新的功能,不改变其原有的结构和功能,原有的功能还是可以使用,跟适配器模式不一样,适配器模式原有的已经不能使用了,装饰器示例比如手机壳 UML类图和代码示例 Circle示原来 ...

  6. JavaScript设计模式之----组合模式

    javascript设计模式之组合模式 介绍 组合模式是一种专门为创建Web上的动态用户界面而量身制定的模式.使用这种模式可以用一条命令在多个对象上激发复杂的或递归的行为.这可以简化粘合性代码,使其更 ...

  7. Java设计模式——装饰者模式

    JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...

  8. JAVA设计模式--装饰器模式

    装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...

  9. javascript的装饰者模式Decorator

    刚开始看这段代码有点绕,现在回过头来看,so easy! Function.prototype.before = function(beforefn){ var _self = this; retur ...

  10. 从源码角度理解Java设计模式——装饰者模式

    一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...

随机推荐

  1. FFmpeg之Linux下编译与调试

    注:下面的一切都是在 root 模式下进行的,可以不再 root 模式下进行 1. 安装linux的基础环境 基础环境就是编译代码的基础库,Ubuntu联网安装软件很简单,一个语句即可搞定,这里列出语 ...

  2. phpstorm配置了git后Terminal 不能使用显示:git' 不是内部或外部命令,也不是可运行的程序

    问题:在phpstorm上配置好git后,将代码拉了下来 ,但是命令行无法使用显示如图 解决方法:①找到安装git的位置,然后在该目录的子目录下分别找到git-core.bin 两个目录,我的安装在了 ...

  3. LC 718. Maximum Length of Repeated Subarray

    Given two integer arrays A and B, return the maximum length of an subarray that appears in both arra ...

  4. RTSP协议-中文定义

    RTSP协议-中文定义 转自:http://blog.csdn.net/arau_sh/article/details/2982914 E-mail:bryanj@163.com 译者: Bryan. ...

  5. Jmeter性能测试工具的使用(Web性能测试)

    Jmeter性能测试工具的使用(Web性能测试) 1.下载 http://pan.baidu.com/s/1o7p18Ye 该软件不用安装,直接解压打开即可使用. 2.使用 这里就在win下进行,图形 ...

  6. CentOS8 缺少 libglade2 安装包的回避方法

    某些gtk2应用程序需要libglade2安装包,但不知为何CentOS的yum仓库里没有此包, 经测试,可手动安装CentOS7的rpm包安装解决. 更新:使用下面一行即可.sudo yum ins ...

  7. composer install与composer update的区别

    1.composer install install 命令从当前目录读取 composer.json 文件,处理了依赖关系,并把其安装到 vendor 目录下. php composer.phar i ...

  8. iptable和tcpdump的先后顺序

    tcpdump是一个用来抓取linux网络数据包的工具,而iptables是linux上的防火墙工具,两者之间的顺序是: Wire -> NIC -> tcpdump -> netf ...

  9. 学校或公司转ISP -boardband (上网公司)注意事项记录

    如果学校或公司轉boardband , 1. 要更新 domain IP (亦可以轉移domain 去新ISP公司, 要HKDNR 登入名稱和密碼,可問舊ISP即boardband 公司或域名管理方要 ...

  10. 【AMAD】django-activity-stream

    动机 简介 个人评分 动机 为你的django站点生成活动流(类似facebook feed).用户可以在个人feed页面看到订阅的人的活动流. 简介 django-activity-stream1中 ...