• JavaScript设计模式(装饰者模式)


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

    假设我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可以升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时可以发射导弹,升到第三级时可以发射原子弹
    // 首先是原始的飞机类
    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 与它的实体之间的关系),这种关系 可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确 定对象的全部功能时。代理模式通常只有一层代理本体的引用,而装饰者模式经常会形成一条 长长的装饰链。
  • 相关阅读:
    shop++之language
    shop++改造之ResponseEntity的坑
    shop++改造之Filter类
    mysql关联模糊查询他表字段
    mysql一张表多个字段关联另一张表查询
    html页面导出为excel表格
    layui打印html页面转成pdf
    jQuery视频格式的验证
    jQuery图片灯箱和视频灯箱
    空间谱专题16:信号个数估计
  • 原文地址:https://www.cnblogs.com/angelatian/p/11858793.html
Copyright © 2020-2023  润新知