学学AOP之装饰者模式

装饰者,英文名叫decorator. 所谓的"装饰",从字面能够很容易的理解出,就是给 土肥圆,化个妆,华丽的转身为白富美,但本体仍是土肥圆。 ajax

说人话.
咳咳~编程

在js里面一切都是对象,并且函数就是一等对象。 在普通的Object中,咱们能够很容易的添加属性或者其余方法,固然函数也不例外。 可是,这样作的后果就是,咱们会不断的改变本体,就像把凤姐送去作整形手术同样。 固然,结果有好有坏,也许凤姐就是下一个angelababy,也许... 因此,为了咱们代码的纯洁度(就算你是丑小鸭), 咱们能够不动刀子的状况下,让你变得又白又美。安全

引用装饰

这个是装饰的初级阶段,就是抹点粉而已。 在js中,咱们叫作引用装饰。app

talk is cheap, show u code框架

//咱们给jimmy函数额外添加其余的功能
var jimmy = function(){
    console.log("jimmy");
}
var _jimmy = jimmy;
jimmy = function(){
    _jimmy();
    console.log("I love HuaHua");
}
jimmy();

这个的应用场景主要就是在多人协做和框架设计里面。好比,李冰岩已经使用了onload函数,可是,小舟又想使用onload函数。 这样会形成一个问题,若是小舟直接改动的话,他须要看的是李冰岩写的蜜汁代码,并且还要防止不会引发错误。这无疑是很困难的,因此在这里,可使用引用装饰,来给onload在添加一层。函数

//这是小李的蜜汁代码
var xiaoLi = function(){
    console.log("蜜汁代码");
}
window.onload = xiaoLi;  //小李已经绑定好onload函数了
//接下来小舟须要改动onload代码
var fn = window.onload;
var xiaoZhou = function(){
    fn();
    conosle.log("整洁代码");
}
window.onload = function(){  //根据onload的特性,直接覆盖掉小李的code
    xiaoZhou();
}

因此引用装饰的用处仍是蛮大的。
大你妹啊~~
啊。。。。
(另外一Me) 咱们来分析一下,上面那个引用模式有什么弊端(好处已经都知道了).
首先,咱们使用引用模式的时候,一定会添加一个多余的引用对象,好比上文的"fn".
并且随着你程序链的增长,中间对象必定会和你节点同等数量的。固然,起名我就不说了,关键是,一大堆无用的代码放在那里,感受很不爽的。 因此,这里引入AOP的装饰模式.工具

AOP装饰

亲切,熟悉,完美。 咱们见过AOP应该不止一次了,在职责链模式使用过,在迭代器模式使用过等等。使用这么屡次,好像尚未对AOP作一些基本解释呢?因此,这里给你们咻咻.
AOP中文名叫面向切面编程。 先说一下这个名词,“面向”这词应该不用解释,关键"切面"是什么鬼。 若是你们作过sangwich,应该就知道,首先咱们买来一块面包,须要将面包切开,而后在切面上面加上一些flavoring,好比蔬菜,火腿,培根之类的。 恩,对比js程序来讲,一个程序链就至关于你买回来的面包,flavoring就是你想加的功能函数,如何将函数正确的放置在程序链中合适的位置,这就是AOP作的事情。
首先,再次将两个动态函数咻咻:性能

Function.prototype.after = function(fn){
    var _this = this;
    return function(){
        var res = _this.apply(this,arguments);
        fn.apply(this,arguments);
        return res;
    }
}
Function.prototype.before = function(fn){
    var _this = this;
    return function(){
        fn.apply(this,arguments);  
        return    _this.apply(this,arguments);
    }
}

这两个函数的组合构成了js中AOP模式的精华.而AOP最经常使用的就是讲与业务逻辑无关的功能动态织入到主程序中。测试

talk is cheap , show u codethis

举个栗子吧: 使用AOP计算程序运行事件

//纯手写计算函数运行事件
function factorial(n) {  //最基本的阶乘计算
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
function calTime(n){
    var start = new Date().getMilliseconds();
    factorial(n);
    console.log(new Date().getMilliseconds() - start+"ms");
}
calTime(1000);

能够得出耗费的时间,可是,若是还有其余的函数须要测试,那么这么作的意义并无很大的价值。咱们使用AOP进行重构。

function factorial(n) {  //最基本的阶乘计算
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
var calTime = (function(){
    var start;
    return    function(){
        if(!start){  //给开始时间赋值
            start = new Date().getMilliseconds();
        }else{
            console.log(new Date().getMilliseconds()-start+"ms");
            start = undefined;
        }
    }
})();
var calcu = factorial.before(calTime).after(calTime)(200);

这样很好的将计时功能从业务逻辑中提取出来,并且看着真的颇有angelababy的味道诶.
使用AOP的时候须要注意一点就是,before&after执行完后,返回的结果都是第一个函数的内容。

var result = function(){
    return 1;
}.before(function(){
    return 2;
}).after(function(){
    return 3;
});
console.log(result());  //1

咱们大体的了解了AOP的用法和理论,能够针对于开头所说的例子进行重构.

window.onload = function(){
    console.log("小李的蜜汁代码");
}
var fn = window.onload;
fn.before(function(){
    console.log("整洁代码");
});
window.onload = fn;

看起来,比上面那个栗子清晰不少,并且使用before和after也十分利于代码的阅读。

实例讲解AOP装饰

上面那个例子,只能算是AOP装饰模式的一个不起眼的角落。 AOP引用的场景在js中,或者说在任何一门语言中都是大放光彩的。 在js中,"细粒度"对象是程序中复用性最高的对象,能把对象用细粒度的形式表示,那么AOP无疑是最佳的选择。
在写业务逻辑的时候,咱们最大的问题就是判断逻辑,使用大量的if语句,而这均可以通过思考巧妙化解。好比,我在写购买课程的时候就会遇到一些逻辑。 只有当课程数目符合要求的时候,购买的效果才能有效.
按正常的业务逻辑编写

var buy = function(){
    if(confirm()){  //验证购买信息是否合法
        http.buyCourse('xxx');  //发起请求
    }
}
var confirm = function(){
    console.log("验证购买数量");
}
document.querySelector(".buy").addEventListener("click",function(){
    buy();
},false);

使用AOP装饰模式重构后

var buy = function(){
    http.buyCourse("xxx"); //给后台发起请求,验证
}
var confirm = function(){
    console.log("验证购买数量");
}
var buy = buy.before(confirm);
document.querySelector(".buy").addEventListener("click",function(){
    buy();
},false);

美美代码的 满满即视感!!!
不够,老板,再来份糖炒栗子~
好嘞~
这里咱们只是获取函数的结果,那咱们想直接干预传递的参数,能够吗?
固然能够。
咱们研究一下,before的内部构造(after是同样的)

Function.prototype.before = function(fn){
    var _this = this;
    return function(){
        fn.apply(this,arguments);  
        return    _this.apply(this,arguments);
    }
}

这里,因为arguments是引用类型,若是fn改变了arguments,则会反映到_this.apply的arguments也会发生改变。 而这个应用场景就是,给ajax的地址添加上你须要的参数。
在实际项目中,一开始的接口,就是一个普普统统的地址,发请求,而后获取参数。

http.ajax(url,type).then(...)

想这样的使用,可是某天,你的leader提升了要求等级,将地址后面都加上一个token参数,或者说一个口令的md5或sha1的计算值,我想,这尼玛工做量应该不小。
固然,咱们能够直接将url进行传递。

var http = {
    ajax1(url){
        url += param.getToken();
        sendAjax(url);
    },
    ajax2(url){
        ...
    }
    ...
}

并且,万一哪天你的leader说,哎,这样作安全性仍是不过高,要不加两个token混淆一下。
啊~啊~啊~啊~(混淆你妹啊,过不过年啦)
若是你继续这么写,我相信,年终奖是有的,可是,春运火车票你估计是摸不着了。
因此可使用AOP进行动态织入。要知道,参数,我AOP也是能够动的。

function dealUrl(url){
    url+=param.getToken();
}
http.ajax = http.ajax.before(dealUrl);
http.ajax("www.example.com");   //此时的Url = www.example.com?token=23jkfd3kjfdksjfkjds

并且,这个处理url函数,我也是能够扔到任意一个js文件里面复用的耶.
棒!!!
我AOP能够动你要的参数,并且,我还能够把个人结果给你是咻咻,若是我不让你执行,你永远也不会执行,哈哈哈哈~~~~
对不起,,上面那段是我意淫AOP说的。。。 其实AOP能够算是万能的配置工具,好比表单验证吧。 咱们常常会把表单验证和表单结果发送耦合在一块儿。
像这样

var sendRes = function(){
    if(user.userName === ""){
        alert("用户名不能为空~");
        return;
    }else if(user.password === ""){
        alert("密码不能为空~");
        return;
    }
    http.sendUser("xxx");  //验证成功发送用户信息
}

一个函数里面同时含有了两个职责,一个验证一个发送用户信息。 因此咱们如今的主要目的就是解耦。
回想一下,之前表单验证咱们使用策略模式,解耦了验证,这里咱们再次使用。

var sendRes = function(){
    var detect = new Detect();  //策略者模式
    detect.add(user.userName,["notEmpty"]);
    detect.add(user.password,["notEmpty"]);
    var notPass = detect.getResult();
    if(notPass){  //若是没经过
        console.log(notPass);
        return;
    }
    http.sendUser("xxx"); //验证成功发送用户信息
}

可使用上面那个验证,可是结果是,验证和策略模式仍是在一块儿。咱们再使用AOP进行解耦。首先咱们得重构一下before函数

Function.prototype.before = function(fn){
    var _this = this;
    return function(){
        var res = fn.apply(this,arguments);  //值为Boolean,表示是否继续向下传递
        if(res==="next"){  //若是返回不成立,则继续向下传递
            return    _this.apply(this,arguments);
        }
        return res;
    }
}

看到这里,有些同窗应该明白是怎么一回事了。没错,就是根据before里面验证的结果判断是否执行下个发送请求的功能函数。
固然,若是不想污染原型,你也能够自定义一个函数。

var before = function(beforeFn,fn){
    return function(){
        var res = beforeFn.apply(this,arguments);
        if(res==="next"){
            return fn.apply(this,arguments);
        }
    }
}

这样写也是能够的。
咱们先按原型方式写,这样直观一点

var sendRes = function(){
    http.sendUser("xxx"); //验证成功发送用户信息
}
sendRes = sendRes.before(function(){
    var detect = new Detect();  //策略者模式
    detect.add(user.userName,["notEmpty"]);
    detect.add(user.password,["notEmpty"]);
    var notPass = detect.getResult();
    if(notPass){  //若是没经过
        console.log(notPass);
    }
    return "next";
});

能够看出,验证那部分已经彻底和发送用户信息的功能函数彻底给解耦了。 这样不只提升了函数的重用性,并且也让你的代码朝着“细粒度”方向大步前进.

辩证装饰者模式

其实,装饰者模式和职责链模式的形式是彻底同样的,因此,他们的弊端也是相似的。链造的过长,对于性能来讲就是一次rape.因此,仍是那句话,不要为了模式而模式,没有万能的模式。

相关文章
相关标签/搜索