装饰者模式

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

代码例子

     var Plane = function(){}
Plane.prototype.fire = function(){
console.log( '发射普通子弹' );
}
var one = function( plane ){ //装饰类 one
this.plane = plane;
}
one.prototype.fire = function(){
this.plane.fire();
console.log( '发射导弹' );
}
var two = function( plane ){ //装饰类 two
this.plane = plane;
}
two.prototype.fire = function(){
this.plane.fire();
console.log( '发射原子弹' );
} var plane = new Plane();
plane = new one( plane ); //装饰对象会覆盖原来的对象,这么做是因为装饰者模式是为了添加职能,可能这时候的代码量已经很大了,原对象的方法可能在很多地方都调用过,所以我们通过这种方式,追加了功能而不用修改太多代码。
plane = new two( plane ); /**装饰对象调用**/
plane.fire(); /* 发射普通子弹
发射导弹
发射原子弹
*/

从代码我们可以看出,装饰者模式这种给对象动态增加职责的方式,并没有真正地改动对象自身。

从形式上来看,装饰者模式就好像给原来的对象包裹了一层,产生一个新的对象,同时,保留了接口统一。装饰类的作用就是接受一个对象,然后把它变成功能更多的对象。

装饰类接收原来的对象,会把它保存下来,调用装饰类时,会将本来的对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(fire方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的下一个对象。

从传递请求这一点来看有点类似于职责链,但是职责链是传递请求给正确执行的对象,而这个不但自身会执行,通过装饰类添加的方法也会执行,它这种类似于链子一样的请求调用与其说是链子倒不如说是包裹更合适。装饰类会把原来的对象包裹起来,调用时会一层一层的调用。

更js的实现方法

因为js是动态语言,修改类型,改变对象的属性和方法对它来说太简单了,所以还有一种思路,我们拿到需要调用的方法的引用,然后直接对它进行改造即可。不过这种方式等于直接修改原对象的方法,和上面的方法哪个更好,我说不上来。

     var plane = {      //原对象
fire: function(){
console.log( '发射普通子弹' );
}
}
var add_one = function(){ //想要添加的功能
console.log( '发射导弹' );
}
var add_two = function(){
console.log( '发射原子弹' );
} var fire1 = plane.fire; //存储对象方法的引用
plane.fire = function(){ //改写对象方法
fire1(); //原对象的方法
add_one(); //添加的方法
} var fire2 = plane.fire; //同上
plane.fire = function(){
fire2();
add_two();
} plane.fire(); /* 发射普通子弹
发射导弹
发射原子弹
*/

另外我还有一个不成熟的想法,像第一种实现装饰者的方法里,我们覆盖了原来对象的变量。使得所有调用对应方法的位置都会执行添加的函数,但是如果我们不是全都需要而是有的地方需要有的地方不需要呢?

我有两个思路,一个是在装饰类里重写方法时进行判断,另一个是把原来的对象单独给个变量,和装饰类加工过的对象有所区分,不需要执行新功能的地方,就用原来的对象调用。

感觉好像第一个思路好一点?

装饰函数

在js中,函数也是对象,只要是对象自然可以实现装饰者。

     var a=function(){
alert(1);
} var _a=a; //保存函数引用 a=function(){ //重写函数
_a(); //原本的函数引用
alert(2); //新添加的功能
}

又比如我们想给window绑定onload事件,但是又不确定这个事件是不是已经被其他人绑定过,为了避免覆盖掉之前的window.onload函数中的行为,我们一般都会先保存好原先的window.onload,把它放入新的window.onload 里执行:

     window.onload = function(){
alert (1);
}
var _onload = window.onload || function(){}; //没有绑过onload,让变量为空的函数 window.onload = function(){ //重写
_onload();
alert (2); //追加的代码
}

可能的坑:this

保存引用再重写,可能会带来this绑定错误的问题,因为我们保存的引用中的this和当前执行环境的this可能完全不同了,重写的函数有时候上下文其实已经改变了,如果上下文改变,this的指向也就改变了,所以它有可能没有指向正确的对象。

     var _getElementById = document.getElementById;  //这个是document这个对象的方法,其内部this,指向document,我们把它的引用保存到_getElementById这个全局变量里
document.getElementById = function( id ){ //重写函数
alert (1);
return _getElementById( id ); // 返回了保留的函数,但是返回的函数是全局函数,this指向window,没有指向正确的document对象
}
var button = document.getElementById( 'button' ); //报错,Uncaught TypeError: Illegal invocation

又比如:

     var name="大白";
var a={
name:"如花",
getname:function(){
console.log(this.name);
}
} var get=a.getname; //保存引用 get(); //大白
a.getname(); //如花

可以看到,调用函数时的环境改变,虽然保存了引用,却没得到我们想要的结果。(我们想获得如花)

如果遇到这种情况,也就只能手动更改(apply),或者控制在同一个上下文环境中添加职责。

比如上面的代码,我们改成:

    get.apply(a);  //如花

就可以获得正确的结果。

第一个例子也是同理

     var _getElementById = document.getElementById;
document.getElementById = function(){
alert (1);
return _getElementById.apply( document, arguments ); //apply大显身手,指定上下文为document
}
var button = document.getElementById( 'button' );

优缺点

装饰者模式可以说是AOP思想的实例。优点:解耦,追加功能不用修改原本函数,更为灵活方便。缺点:会叠加函数的作用域,如果装饰的链条过长,性能上也会受到一些影响。

《javascript设计模式与开发实践》阅读笔记(15)—— 装饰者模式的更多相关文章

  1. javascript设计模式与开发实践阅读笔记(4)——单例模式

    定义 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 具体来说,就是保证有些对象有且只有一个,比如线程池.全局缓存.浏览器中的window 对象等.在js中单例模式用途很广,比如登录 ...

  2. javascript设计模式与开发实践阅读笔记(8)——观察者模式

    发布-订阅模式,也叫观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 在JavaScript开发中,我们一般用事件模型来替代传统的观察者模式. ...

  3. javascript设计模式与开发实践阅读笔记(7)——迭代器模式

    迭代器模式:指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. 迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺 ...

  4. javascript设计模式与开发实践阅读笔记(6)——代理模式

    代理模式:是为一个对象提供一个代用品或占位符,以便控制对它的访问. 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对 ...

  5. javascript设计模式与开发实践阅读笔记(5)——策略模式

    策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 我的理解就是把各种方法封装成函数,同时存在一个可以调用这些方法的公共函数.这样做的好处是可以消化掉内部的分支判断,使代码效率 ...

  6. javascript设计模式与开发实践阅读笔记(9)——命令模式

    命令模式:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 说法很复 ...

  7. javascript设计模式与开发实践阅读笔记(11)—— 模板方法模式

    模板方法模式: 由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类.通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序.子类通过继承这个抽象类,也继 ...

  8. JavaScript设计模式与开发实践——读书笔记1.高阶函数(上)

    说来惭愧,4个多月未更新了.4月份以后就开始忙起来了,论文.毕设.毕业旅行等七七八八的事情占据了很多时间,毕业之后开始忙碌的工作,这期间一直想写博客,但是一直没能静下心写.这段时间在看<Java ...

  9. 《JavaScript设计模式与开发实践》笔记第八章 发布-订阅模式

    第八章 发布-订阅模式 发布-订阅模式描述 发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 发布-订阅模式可以广泛应用于 ...

  10. JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)

    上部分主要介绍高阶函数的常见形式,本部分将着重介绍高阶函数的高级应用. 1.currying currying指的是函数柯里化,又称部分求值.一个currying的函数会先接受一些参数,但不立即求值, ...

随机推荐

  1. 利用Java泛型实现简单的泛型方法

    问题:编写一个泛型方法add(),当传入不同数字类型的值时,能够进行加法运算,(如可以传入int.long.float.double类型,但要对传入的值做一定的限定,如必须是数字). 解决:利用泛型编 ...

  2. 在Jenkins中配置执行远程shell命令

    1.想要 远程登录到linux服务器并执行相应的shell脚本,需要在jenkins上安装插件enkins SSH plugin 2. 安装了这个插件后,进入系统的配置管理中配置 SSH remote ...

  3. leaflet渲染mapbox gl的矢量数据

    准备条件 1.mapbox-gl.js mapbox-gl.css 2.leaflet-mapbox-gl.js https://github.com/mapbox/mapbox-gl-leaflet ...

  4. Java内存区域之程序计数器--《深入理解Java虚拟机》学习笔记及个人理解(一)

    Java虚拟机程序计数器 在书上的P39页 程序计数器干嘛的? 有了它,字节码解释器才可以知道下一条要执行的字节码指令是哪个. 无论是取下一条指令还是分支.循环.跳转.中断.线程恢复,都需要这个程序计 ...

  5. mybatis动态insert,update

    1. 动态update UPDATE ui.user_question_section_xref <set> reviewer = #{0}, score = #{1} , last_up ...

  6. JS基础三

    1.delete删除对对象的属性和方法的定义.强制解除对它的引用,将其设置为 undefined delete 运算符不能删除开发者未定义的属性和方法. 2.void 运算符对任何值返回 undefi ...

  7. const 相关知识 const和指针、const和引用

    以前老是对const概念不清不楚,今天算是好好做个笔记总结一下.以下内容包括1)常量指针(指针本身是常量),2)指针常量(指针指向的是常量对象),3)常量引用,4)const成员函数. 常量指针,指针 ...

  8. 笔记:Spring Cloud Eureka 服务发现与消费

    服务发现与消费,其服务发现的任务是由Eureka的客户端完成,而服务的消费任务由Ribbon.JerseyClient等完成,Ribbon是一个基于HTTP和TCP的客户端负载均衡器:使用Jersey ...

  9. day1 安装jdk8环境及第一个java程序

    安装jdk8 第一步:下载jdk安装包,我们这里下载orical官网的jdk8版本.

  10. Redis登录密码设置

    1. 更改Redis.conf配置 # requirepass foobared 去掉注释,foobared改为 自己的password , 我测试的时候用的是默认的 foobared 2.启动red ...