专栏首页AlbertYang的编程之路设计模式(11)[JS版]-JavaScript中的注解之装饰器模式

设计模式(11)[JS版]-JavaScript中的注解之装饰器模式

1 什么是装饰器模式?

装饰器模式模式动态地扩展了(装饰)一个对象的行为,同时又不改变其结构。在运行时添加新的行为的能力是由一个装饰器对象来完成的,它 "包裹 "了原始对象,用来提供额外的功能。多个装饰器可以添加或覆盖原始对象的功能。装饰器模式属于结构型模式。和适配器模式不同的是,适配器模式是原有的对象不能用了,而装饰器模式是原来的对象还能用,在不改变原有对象结构和功能的前提下,为对象添加新功能。

装饰器的一个例子是安全管理,其中业务对象被赋予了额外的访问权限,这取决于经过认证的用户的权限。例如,人力资源经理得到一个雇员对象,该对象已经附加(即装饰了)查看雇员的工资记录权限,这样该员工就可以查看工资信息了。装饰器通过允许运行时更改,为静态类型的语言提供灵活性。但是,JavaScript是一种动态语言,并且在运行时扩展对象的能力已融入该语言本身。

2 装饰器模式的主要参与者有哪些

参与该模式的对象有:

客户端(Client) :维护一个对被装饰的组件的引用。 组件(Component) :添加了附加功能的对象。 装饰器(Decorator) : 1.通过保持对Component的引用来 "包装 "它。 2. 定义了一个符合Component接口的接口。 3.实现附加功能(图中addedMembers)。

3 代码实现

在下面的代码中,一个User对象被一个DecoratedUser对象装饰(增强),它扩展了User的地址属性。因为原始接口必须保持不变,所以user.name会被分配给this.name。另外,DecoratedUser的say方法隐藏了User的say方法。

这是装饰器模式的经典实现,但是JavaScript本身的一些语法,就可以更有效的在运行时扩展对象,所以在实际开发中我们一般不会用到这种方法。日志函数用来记录和显示结果。

<!DOCTYPE html>
<html>
        <head>
                <meta charset="utf-8">
                <title>装饰器模式:公众号AlbertYang</title>
        </head>
        <body>
        </body>
        <script>
                var User = function(name) {
                        this.name = name;

                        this.say = function() {
                                log.add("我是" + this.name);
                        };
                }

                var DecoratedUser = function(user, city, street) {
                        this.user = user;
                        this.name = user.name; // 确保接口保持不变
                        this.city = city;
                        this.street = street;

                        this.say = function() {
                                log.add("我是" + this.name + ", 住在" + this.city + ", " +
                                        this.street);
                        };
                }

                // 日志函数
                var log = (function() {
                        var log = "";

                        return {
                                add: function(msg) {
                                        log += msg + "\n";
                                },
                                show: function() {
                                        console.info("%c%s", "color:red; font-size:18px", log);
                                        log = "";
                                }
                        }
                })();

                function run() {

                        var user = new User("张三");
                        user.say();

                        var decorated = new DecoratedUser(user, "上海", "宝山路街道");
                        decorated.say();

                        log.show();
                }
                run();
</script>
</html>

4 实例应用

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>装饰器模式:公众号AlbertYang</title>
  </head>
  <body>
    <script>
      //实例1 - 多重onload绑定
      function addLoadEvent(fn) {
        var oldEvent = window.onload;
        if (typeof window.onload != 'function') {
          window.onload = fn;
        } else {
          window.onload = function() {
            oldEvent();
            fn();
          };
        }
      }

      function fn1() {
        console.log('加载函数1');
      }

      function fn2() {
        console.log('加载函数2');
      }

      function fn3() {
        console.log('加载函数3');
      }
      addLoadEvent(fn1);
      addLoadEvent(fn2);
      addLoadEvent(fn3);

      //实例2 - 前置/后置处理函数(AOP面向切面编程)
      Function.prototype.before = function(beforfunc) {
        var self = this;//用来保存调用这个函数的引用,如myFunc调用此函数,则self指向myFunc
        var outerArgs = Array.prototype.slice.call(arguments, 1);

        return function() {//返回一个函数,相当于一个代理函数,也就是说,这里包含了原函数和新函数,原函数指的是myFunc,新函数指的是beforfunc
          var innerArgs = Array.prototype.slice.call(arguments);

          beforfunc.apply(this, innerArgs);//修正this的指向,将this指针指向beforfunc,将myFunc接收的参数传给beforfunc处理。
          self.apply(this, outerArgs);//执行原函数
        };
      };

      Function.prototype.after = function(afterfunc) {
        var self = this;
        var outerArgs = Array.prototype.slice.call(arguments, 1);

        return function() {
          var innerArgs = Array.prototype.slice.call(arguments);

          self.apply(this, outerArgs);
          afterfunc.apply(this, innerArgs);
        };
      };

      var func = function(name) {
        console.log('我是' + name);
      };
      var beforefunc = function(age) {
        console.log('我' + age + '岁了');
      };
      var afterfunc = function(gender) {
        console.log('我是一个' + gender);
      };

      var beforeFunc = func.before(beforefunc, '张三');
      var afterFunc = func.after(afterfunc, '张三');

      beforeFunc('12');
      afterFunc('男人');

      //实例3 - 计算函数执行用时
      function log(func) {
        return function(...args) {
          const start = Date.now();
          let result = func(...args);
          const used = Date.now() - start;
          console.log(`调用${func.name} (${args})函数用了 ${used} 毫秒。`);
          return result;
        };
      }

      function calculate(times) {
        let sum = 0;
        let i = 1;
        while (i < times) {
          sum += i;
          i++;
        }
        return sum;
      }

      runCalculate = log(calculate);
      let result = runCalculate(100000);
      console.log(result);
</script>
  </body>
</html>

5 ES7 中的 decorator

在ES7中提供了一种类似于java注解的语法糖来实现装饰器模式。decorator的实现依赖于ES5的Object.defineProperty方法来进行扩展和封装的。装饰器是一种函数,写法是 @+函数名。在使用它之前需要引入babel模块 transform-decorators-legacy 编译成 ES5 或 ES6。

@testable
class MyTestableClass {
// ...
}

function testable(target) {
   target.isTestable = true;
}

MyTestableClass.isTestable // true


@decorator
class A {}

// 等同于
class A {}
A = decorator(A) || A;

上面代码中,@testable就是一个装饰器。它修改了MyTestableClass这个类的属性,为它加上了静态属性isTestable。testable函数的参数target是MyTestableClass类本身。装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。

我们来做一个常用的mixins混合装饰器,来把一个类里面属性和方法全部添加到另一个类上

function mixins(...list) {
    return function (target) {
      Object.assign(target.prototype, ...list)
    }
  }
  
  const Foo = {
    foo() { alert('foo') }
  }
  
  @mixins(Foo)
  class MyClass {}
  
  let obj = new MyClass();
  obj.foo() // 'foo'

装饰方法,让某个方法只读,不能修改

function readonly(target, name, descriptor){
    // descriptor属性描述对象 (Object.defineProperty 会用到)
    // descriptor对象原来的值如下
    // {
    //   value: specifiedFunction,
    //   enumerable: false,
    //   configurable: true,
    //   writable: true
    // };
    descriptor.writable = false;
    return descriptor;
  }
  
  class Person {
      constructor() {
          this.first = 'A'
          this.last = 'B'
      }
  
      @readonly
      testname() { return `${this.first} ${this.last}` }
  }
  
  var p = new Person()
  console.log(p.testname())
  p.testname = function () {} // 这里会报错,因为 name 是只读属性

实现一个日志打印装饰器

function log(target, name, descriptor) {
    var oldValue = descriptor.value;
    // name 是修饰的方法名字
    descriptor.value = function() {
      console.log(`Calling ${name} with`, arguments);
      return oldValue.apply(this, arguments);
    };
  
    return descriptor;
  }
  
  class Math {
    @log
    add(a, b) {
      return a + b;
    }
  }
  
  const math = new Math();
  const result = math.add(2, 4);
  console.log('result', result);

装饰类的时候,一般主要是看target(装饰对象)第一个参数。装饰方法的时候,一般主要看的是descriptor(描述)第三个参数。

core-decorators.js是一个第三方模块,提供了几个常见的装饰器,通过它可以更好地理解装饰器。

官网文档:https://github.com/jayphelps/core-decorators.js

6 总结

装饰器模式是一种常见的结构型模式,在不改变类或对象本身结构的情况下,在程序的运行期间动态的为对象或类添加功能。与继承相比,装饰者模式是一种更轻便灵活的做法。Decorator 虽然原理非常简单,却可以实现很多实用又方便的功能,目前前端领域很多框架和库都在大规模使用这个特性,像 mobx中@observable、Angular中的大量应用都证明了其高可用性。个人觉得在一些开发框架中尝试加入装饰器可以提供更简洁以及高效的代码质量。

今天的学习就到这里,你可以使用今天学习的技巧来改善一下你曾经的代码,如果想继续提高,欢迎关注我,每天学习进步一点点,就是领先的开始。

本文分享自微信公众号 - AlbertYang(AlbertYang666),作者:AlbertYang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript设计模式之装饰器模式

    手机壳就是装饰器,没有它手机也能正常使用,原有的功能不变,手机壳可以减轻手机滑落的损耗。

    FinGet
  • 设计模式之装饰器设计模式

    Java的IO流使用了一种装饰器设计模式。它将IO流分成底层节点流和上层处理流,其中节点流用于和底层的物流存储结点直接关联——不同的物流节点获取该结点流的方式可...

    二十三年蝉
  • 设计模式之 装饰器模式

    tanoak
  • 设计模式之装饰器模式

    在日常生活中,装饰器模式的场景更多是的打扮了,一个妹子,嫌弃自己的脸长得不够漂亮,想换张脸很困难,但是化化妆还是很容易的(当然了,化妆也分男女的,此处指的是女士...

    Edison.Ma
  • 设计模式之装饰器模式

    Attach additional responsibilities to an object dynamically keeping the same int...

    beginor
  • PHP设计模式之装饰器模式

    工厂模式告一段落,我们来研究其他一些模式。不知道各位大佬有没有尝试过女装?据说女装大佬程序员很多哟。其实,今天的装饰器模式就和化妆这件事很像。相信如果有程序媛M...

    硬核项目经理
  • dart设计模式之装饰器模式

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

    Weaster
  • 图解Java设计模式之装饰者模式

    1)咖啡种类/单品咖啡 :Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡) 2)调料 :Milk...

    海仔
  • 设计模式 ☞ 结构型模式之装饰器模式

      装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。即允许通过...

    Demo_Null
  • 深入理解JavaScript系列(29):设计模式之装饰者模式

    装饰者提供比继承更有弹性的替代方案。 装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数)。

    用户4962466
  • 设计模式之简单理解装饰器模式与运用

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

    Dream城堡
  • 浅谈JS中的装饰器模式

    装饰器(Decorator)是ES7中的一个新语法,使用可参考阮一峰的文章。正如其字面意思而言,它可以对类、方法、属性进行修饰,从而进行一些相关功能定制。它的写...

    IMWeb前端团队
  • PHP设计模式之装饰器(装饰者)模式(Decorator)入门与应用详解

    本文实例讲述了PHP设计模式之装饰器(装饰者)模式(Decorator)入门与应用。分享给大家供大家参考,具体如下:

    砸漏
  • 浅谈设计模式(二):装饰器模式|中介模式|原型模式

    装饰器模式可用来给一个类动态添加功能,将其装饰成一个新的类。这就是装饰器的概念。看到这里我们可能会想,要达到这种效果,我们用子类继承父类不就可以了吗? 没错装饰...

    啦啦啦321
  • PHP设计模式之装饰器模式定义与用法详解

    本文实例讲述了PHP设计模式之装饰器模式定义与用法。分享给大家供大家参考,具体如下:

    用户8660814
  • 设计模式(6)-装饰器(认识程序中的装饰器)

    之前已经看过装饰器模式,但是感觉不是很清晰,但是有一种情况下出的代码,一定是装饰器。 Widget* aWidget = new BorderDecorator...

    cloudskyme
  • php设计模式之装饰模式应用案例详解

    和之前一样,我们定义了一个抽象基类(ProcessRequest)、一个具体的组件(MainProcess)和一个抽象装饰类(DecorateProcess)。...

    砸漏
  • OOAD-设计模式(四)结构型模式之适配器、装饰器、代理模式

    前言   前面我们学习了创建型设计模式,其中有5中,个人感觉比较重要的是工厂方法模式、单例模式、原型模式。接下来我将分享的是结构型模式! 一、适配器模式 1.1...

    用户1195962
  • 测试工具中的设计模式实例谈---装饰模式

    理想的装饰器模式要求对客户端透明,只改变行为,不改变接口。 ##Hamcrest中的装饰模式 在Hamcrest中,为了表达更为复杂的Matcher逻辑,或者...

    Antony

扫码关注云+社区

领取腾讯云代金券