zoukankan      html  css  js  c++  java
  • 设计模式(11)[JS版]-JavaScript中的注解之装饰器模式

    目录

    1 什么是装饰器模式?

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

    3 代码实现

    4 实例应用

    5 ES7 中的 decorator

    6 总结


    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 + "
    ";
                                    },
                                    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(描述)即第三个参数。

         ES7 中的 decorator还有很多其他的用法,由于时间有限,在这里就不展开讲了,如果想要了解更多ES7 中的 decorator,可以去系统学习一下,它的实现原理和用法。

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

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

    6 总结

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

    今天的学习就到这里,你可以使用今天学习的技巧来改善一下你曾经的代码,如果想继续提高,欢迎关注我,每天学习进步一点点,就是领先的开始。如果觉得本文对你有帮助的话,欢迎点赞,评论,转发!!!

     

  • 相关阅读:
    谈Vite在Electron环境下吃花卷拉馒头的现象
    公司只提供签名服务,不提供证书文件,如何打包Electron应用
    vue3微前端架构——基于蚂蚁qiankun框架
    Vite ❤ Electron——基于Vite搭建Electron+Vue3的开发环境【一】
    dotnet core 高CPU lldb篇
    Openshift入门(转)
    使用 dotnet test 和 xUnit 在 .NET Core 中进行 C# 单元测试
    ASP.NET Core 中的单元测试控制器逻辑
    ASP.NET Core 中间件
    Precise Computation of CLR Object Size
  • 原文地址:https://www.cnblogs.com/yangxianyang/p/13675543.html
Copyright © 2011-2022 走看看