精华内容
下载资源
问答
  • javascript装饰器模式

    2020-12-28 18:12:56
    javascript装饰器模式 function PlaneFactory() { this.decorate_list = []; } //创建对象接口 PlaneFactory.create = function (type, x, y) { //判断prototype上是否存在该类型的子类 if (PlaneFactory....

    javascript装饰器模式

     function PlaneFactory() {
         this.decorate_list = [];
     }
    
     //创建对象接口
     PlaneFactory.create = function (type, x, y) {
         //判断prototype上是否存在该类型的子类
         if (PlaneFactory.prototype[type] == undefined) {
             throw 'not this constructor!';
         }
         if (PlaneFactory.prototype[type].prototype.__proto__ != PlaneFactory.prototype) {
             // PlaneFactory.prototype[type].prototype.__proto__ = PlaneFactory.prototype;
             PlaneFactory.prototype[type].prototype = new PlaneFactory();
         }
         let arg = Array.from(arguments).slice(1);
         return new PlaneFactory.prototype[type](...arg);
     }
    
     PlaneFactory.prototype.die = function () {
         console.log('boom');
     }
    
     PlaneFactory.prototype.touch = function () {
         this.blood -= 50;
         console.log(this.blood);
         if (this.blood === 0) {
             this.die();
         }
     }
    
     PlaneFactory.prototype.decorators = {
         increaseOneBlood(obj) {
             obj.blood += 50;
         },
         increaseTwoBlood(obj) {
             obj.blood += 100;
         },
         decreaseOneBlood(obj) {
             obj.blood -= 50;
         },
         decreaseTwoBlood(obj) {
             obj.blood -= 100;
         }
    
     }
     
     PlaneFactory.prototype.decorate = function (decorate) {
         this.decorate_list.push(decorate);
     }
     
     PlaneFactory.prototype.delete = function (decorate) {
         let index = this.decorate_list.indexOf(decorate)
         this.decorate_list.splice(index,1);
     }
    
     PlaneFactory.prototype.empty = function () {
         this.decorate_list.length = 0;
     }
    
     PlaneFactory.prototype.update = function () {
         this.decorate_list.forEach(item => {
             this.decorators[item](this);
         })
     }
    
     PlaneFactory.prototype.SmallPlane = function (x, y) {
         this.x = x;
         this.y = y;
         this.width = 100;
         this.height = 125;
         this.blood = 100;
         this.name = 'SmallPlane';
     }
    
     PlaneFactory.prototype.BigPlane = function (x, y) {
         this.x = x;
         this.y = y;
         this.width = 150;
         this.height = 175;
         this.blood = 200;
         this.name = 'BigPlane';
     }
    
     PlaneFactory.prototype.AttachPlane = function (x, y) {
         this.x = x;
         this.y = y;
         this.width = 100;
         this.height = 150;
         this.blood = 150;
         this.name = 'AttachPlane';
         this.attach = function () {
             console.log('biu~biu~biu~');
         }
     }
    
     const ap = new PlaneFactory.create('AttachPlane', 20, 30);
     ap.decorate('increaseOneBlood');
     ap.decorate('increaseTwoBlood');
     ap.delete('increaseTwoBlood');
     // ap.empty();
     ap.update();
     // ap.decorate('increaseOneBlood');
     const bp = new PlaneFactory.create('BigPlane', 30, 40);
     const sp = new PlaneFactory.create('SmallPlane', 10, 15);
    
    展开全文
  • JavaScript装饰器模式

    2020-12-26 16:33:22
    适配器模式是原来的接口就不能用了,需要做一个适配之后才能继续使用。 装饰器是原先接口还能用,在此基础上添加一些新功能。(为对象添加新功能,并且不改变原有结构和功能)

    装饰器模式

    适配器模式是原来的接口就不能用了,需要做一个适配之后才能继续使用。
    装饰器是原先接口还能用,在此基础上添加一些新功能。(为对象添加新功能,并且不改变原有结构和功能)

    下面我们来举一个装饰器的例子:

    class Circle {
      draw() {
        console.log('画一个圆形')
      }
    }
    
    class Decorator {
      constructor(circle) {
        this.circle = circle
      }
      draw() {
        this.circle.draw()
        this.setRedBorder(circle)
      }
      setRedBorder(circle) {
        console.log('设置红色边框')
      }
    }
    
    // 测试
    let circle = new Circle()
    circle.draw()
    
    let dec = new Decorator(circle)
    dec.draw()
    

    可以看到,通过装饰器方式获取到的dec,既可以画一个圆,又可以设置红色边框。通过上述代码,其实我们也可以感受到Decorator就像是给Circle加了一层外包装。所以有的时候我们将装饰器也称之为包装器(wrapper)

    我们刚才看到上面的代码样式,其实理解起来不算特别友好。在Spring Boot中,可以通过@的方式来添加装饰器,但目前JavaScript还不支持,不过Typescript中,已经可以作为一个实验性的功能加以支持。不过在JavaScript中,还可以通过安装一些npm包的方式加以支持。下面我们来看看如何写出更容易理解的装饰器代码。


    使用装饰器

    我们首先来看一个简单的例子:

    @testDec
    class Demo {
      // ...
    }
    
    function testDec(target) {
      target.isDec = true
    }
    alert(Demo.isDec)  // true
    

    他的原理等同于

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

    可以看到它其实有点类似于一种包装的形式。

    加参数的装饰器
    function testDec(isDec) {
      return function (target) {
        target.isDec = isDec
      }
    }
    
    @testDec(false)
    class Demo {
    
    }
    alert(Demo.isDec) // false
    
    混合形式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中会用到), 原来的值如下:
      /* 
          value: specifiedFunction,
          enumerate: false,
          configurable: true,
          writable: true  
      */
      descriptor.writable = false
      return descriptor
    }
    
    class Person {
      constructor() {
        this.first = 'A'
        this.last = 'B'
      }
    
      @readonly
      name() {
        return `${this.first} ${this.last}`
      }
    }
    
    let p = new Person()
    console.log(p.name)
    // false
    p.name = function() {
      alert(100)
    }
    

    实现打印日志,然后再执行函数的值:

    function log(target, name, descriptor) {
      let oldValue = descriptor.value 
      descriptor.value = function () {
        console.log(`calling ${name} with`, arguments)
        // oldValue是一个函数,此处是做加法的执行
        return oldValue.apply(this, arguments)
      }
      return descriptor
    }
    
    class Math {
      @log
      add(a, b) {
        return a + b
      }
    }
    
    let math = new Math()
    const result = math.add(2, 4)
    console.log(result) // calling add with Arguments(一个对象) 6
    
    展开全文
  • 1. 什么是装饰器模式? 2. 装饰器模式的原理 3. 装饰器模式的使用场景 3.1 给浏览器事件添加新功能(1) 3.2 给浏览器事件添加新功能(2) 4. 装饰器模式的优缺点 5. 装饰器模式的适用场景 6. 其他相关模式

    JavaScript 设计模式系列文章:

    1. 什么是装饰器模式?

    装饰器模式,又名装饰者模式。它的定义是“在不改变原对象的基础上,通过对其进行包装拓展,使得原有对象可以动态具有更多功能,从而满足用户的更复杂需求”。

    装饰器模式的本质是功能动态组合,即动态地给一个对象添加额外的职责,就增加功能角度来看,使用装饰器模式比用继承更为灵活。好处就是有效地把对象的核心职责和装饰功能区分开,并且通过动态增删装饰去除目标对象中重复的装饰逻辑。

    举一个很简单的例子,我们在买房之后,就可以居住了。但是,往往我们会对房屋进行装饰,通水电、刷漆、铺地板、购置家具,安装家电等等。这样,就让房屋就有了各种各样的特性,刷漆、铺地板之后房子变的更美观了;摆放家具、家电之后,房屋就更加便捷了等等。但是,我们并没有改变房子是用来居住的基本功能,这就是装饰的作用。

    再比如,我们好多人喜欢给手机买手机壳,装上手机壳之后,手机就变得更加耐磨,耐摔,更加好看等,但是我们并没有改变手机的功能,只是对其进行了装饰。

    这两个例子中,都有以下特点:

    • 装饰不影响原有的功能,原有功能可以照常使用;
    • 装饰可以增加多个,共同给目标对象添加额外功能。

    2. 装饰器模式的原理

    装饰器模式的原理:
    在这里插入图片描述
    可以从上图看出,在表现形式上,装饰器模式和适配器模式比较类似,都属于包装模式。在装饰器模式中,一个对象被另一个对象包装起来,形成一条包装链,并增加了原先对象的功能。

    3. 装饰器模式的使用场景

    3.1 给浏览器事件添加新功能(1)

    添加装饰器函数常被用来给原有浏览器或 DOM 绑定事件上绑定新的功能,比如在 onload 上增加新的事件,或在原来的事件绑定函数上增加新的功能,或者在原本的操作上增加用户行为埋点:

    window.onload = function() {
        console.log('原先的 onload 事件 ')
    }
    
    /* 发送埋点信息 */
    function sendUserOperation() {
        console.log('埋点:用户当前行为路径为 ...')
    }
    
    /* 将新的功能添加到 onload 事件上 */
    window.onload = function() {
        var originOnload = window.onload
        return function() {
            originOnload && originOnload()
            sendUserOperation()
        }
    }()
    
    // 输出: 原先的 onload 事件
    // 输出: 埋点:用户当前行为路径为 ...
    

    可以看到通过添加装饰函数,为 onload 事件回调增加新的方法,且并不影响原本的功能,我们可以把上面的方法提取出来作为一个工具方法:

    window.onload = function() {
        console.log('原先的 onload 事件 ')
    }
    
    /* 发送埋点信息 */
    function sendUserOperation() {
        console.log('埋点:用户当前行为路径为 ...')
    }
    
    /* 给原生事件添加新的装饰方法 */
    function originDecorateFn(originObj, originKey, fn) {
        originObj[originKey] = function() {
            var originFn = originObj[originKey]
            return function() {
                originFn && originFn()
                fn()
            }
        }()
    }
    
    // 添加装饰功能
    originDecorateFn(window, 'onload', sendUserOperation)
    
    // 输出: 原先的 onload 事件
    // 输出: 埋点:用户当前行为路径为 ...
    

    3.2 给浏览器事件添加新功能(2)

    下面再看一个场景:点击一个按钮后,如果用户还未登录,就弹窗提示用户“您还未登录哦~”。

    <body>
        <button id='open'>点击打开</button>
        <button id='close'>关闭弹框</button>
    </body>
    <script>
        // 弹框创建逻辑,这里我们复用了单例模式面试题的例子
        const Modal = (function() {
            let modal = null
            return function() {
                if(!modal) {
                    modal = document.createElement('div')
                    modal.innerHTML = '您还未登录哦~'
                    modal.id = 'modal'
                    modal.style.display = 'none'
                    document.body.appendChild(modal)
                }
                return modal
            }
        })()
        
        // 点击打开按钮展示模态框
        document.getElementById('open').addEventListener('click', function() {
            // 未点击则不创建modal实例,避免不必要的内存占用
            const modal = new Modal()
            modal.style.display = 'block'
        })
        
        // 点击关闭按钮隐藏模态框
        document.getElementById('close').addEventListener('click', function() {
            const modal = document.getElementById('modal')
            if(modal) {
                modal.style.display = 'none'
            }
        })
    </script>
    

    后来因为业务需求的变更,要求在弹框被关闭后把按钮的文案改为“快去登录”,同时把按钮置灰。

    这个需求更改看起来比较简单,但是,可能不止有一个按钮有这个需求,那我们有可能还要更该很多代码。况且,直接去修改已有的函数体的话,就违背了“开放封闭原则”;在一个函数体中写很多逻辑,就违背了“单一职责原则”。

    我们要做的就是将原来的逻辑与新的逻辑分离,将旧的逻辑抽离出来:

    // 将展示Modal的逻辑单独封装
    function openModal() {
        const modal = new Modal()
        modal.style.display = 'block'
    }
    

    编写新逻辑:

    // 按钮文案修改逻辑
    function changeButtonText() {
        const btn = document.getElementById('open')
        btn.innerText = '快去登录'
    }
    
    // 按钮置灰逻辑
    function disableButton() {
        const btn =  document.getElementById('open')
        btn.setAttribute("disabled", true)
    }
    
    // 新版本功能逻辑整合
    function changeButtonStatus() {
        changeButtonText()
        disableButton()
    }
    

    然后把三个操作逐个添加open按钮的监听函数里:

    document.getElementById('open').addEventListener('click', function() {
        openModal()
        changeButtonStatus()
    })
    

    这样,就实现了“只添加,不修改”的装饰器模式,使用changeButtonStatus的逻辑装饰了旧的按钮点击逻辑。

    以上是ES5中的实现,ES6中,我们可以以一种更加面向对象化的方式去写:

    // 定义打开按钮
    class OpenButton {
        // 点击后展示弹框(旧逻辑)
        onClick() {
            const modal = new Modal()
            modal.style.display = 'block'
        }
    }
    
    // 定义按钮对应的装饰器
    class Decorator {
        // 将按钮实例传入
        constructor(open_button) {
            this.open_button = open_button
        }
        
        onClick() {
            this.open_button.onClick()
            // “包装”了一层新逻辑
            this.changeButtonStatus()
        }
        
        changeButtonStatus() {
            this.changeButtonText()
            this.disableButton()
        }
        
        disableButton() {
            const btn =  document.getElementById('open')
            btn.setAttribute("disabled", true)
        }
        
        changeButtonText() {
            const btn = document.getElementById('open')
            btn.innerText = '快去登录'
        }
    }
    
    const openButton = new OpenButton()
    const decorator = new Decorator(openButton)
    
    document.getElementById('open').addEventListener('click', function() {
        // openButton.onClick()
        // 此处可以分别尝试两个实例的onClick方法,验证装饰器是否生效
        decorator.onClick()
    })
    

    这里我们把按钮实例传给了 Decorator,以便于后续 Decorator 可以对它为所欲为进行逻辑的拓展。

    4. 装饰器模式的优缺点

    (1)装饰器模式的优点:

    • 可维护性高: 我们经常使用继承的方式来实现功能的扩展,但这样会给系统中带来很多的子类和复杂的继承关系,装饰器模式允许用户在不引起子类数量暴增的前提下动态地修饰对象,添加功能,装饰器和被装饰器之间松耦合,可维护性好;
    • 灵活性好: 被装饰器可以使用装饰器动态地增加和撤销功能,可以在运行时选择不同的装饰器,实现不同的功能,灵活性好;
    • 复用性高: 装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,可以给一个对象增加多个同样的装饰器,也可以把一个装饰器用来装饰不同的对象,有利于装饰器功能的复用;
    • 多样性: 可以通过选择不同的装饰器的组合,创造不同行为和功能的结合体,原有对象的代码无须改变,就可以使得原有对象的功能变得更强大和更多样化,符合开闭原则;

    (2)装饰器模式的缺点:

    • 使用装饰器模式时会产生很多细粒度的装饰器对象,这些装饰器对象由于接口和功能的多样化导致系统复杂度增加,功能越复杂,需要的细粒度对象越多;
    • 由于更大的灵活性,也就更容易出错,特别是对于多级装饰的场景,错误定位会更加繁琐;

    5. 装饰器模式的适用场景

    • 如果不希望系统中增加很多子类,那么可以考虑使用装饰器模式;
    • 需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,这时采用装饰器模式可以很好实现;
    • 当对象的功能要求可以动态地添加,也可以动态地撤销,可以考虑使用装饰器模式;

    6. 其他相关模式

    (1)装饰器模式与适配器模式

    装饰器模式和适配器模式都是属于包装模式,然而他们的意图有些不一样:

    • 装饰器模式: 扩展功能,原有功能还可以直接使用,一般可以给目标对象多次叠加使用多个装饰器;
    • 适配器模式: 功能不变,但是转换了原有接口的访问格式,一般只给目标对象使用一次;

    (2)装饰器模式与组合模式

    这两个模式有相似之处,都涉及到对象的递归调用,从某个角度来说,可以把装饰器模式看做是只有一个组件的组合模式。

    • 装饰器模式: 动态地给对象增加功能;
    • 组合模式: 管理组合对象和叶子对象,为它们提供一致的操作接口给客户端,方便客户端的使用;

    (3)装饰器模式与策略模式

    装饰器模式和策略模式都包含有许多细粒度的功能模块,但是他们的使用思路不同:

    • 装饰器模式: 可以递归调用,使用多个功能模式,功能之间可以叠加组合使用;
    • 策略模式: 只有一层选择,选择某一个功能;
    展开全文
  • javascript设计模式之装饰器模式装饰器模式是一种结构型模式有两种常见的实现方法第一种是利用原始对象原型的decorate方法,实现装饰对象对上一个对象的继承和方法重载。最终的对象是经过一系列装饰的装饰对象。代码...

    javascript设计模式之装饰器模式

    装饰器模式是一种结构型模式

    有两种常见的实现方法

    第一种是利用原始对象原型的decorate方法,实现装饰对象对上一个对象的继承和方法重载。最终的对象是经过一系列装饰的装饰对象。

    代码如下:

    Function Sale (price) {  
      this.price = price || 100;
    }
    
    Sale.decorators = {};
    
    Sale.decorators.fedtax = {  
      getPrice: function () {  
          price = this.uber.getPrice();
          return price*2;
        }
    };
    
    Sale.prototype.getPrice = function () {  
      Return this.price;
    };
    
    Sale.prototype.decorate = function (decorator) {  
      Var F = function () {},
              overrides = this.constructor[decorator],
              I,newObj;
    
        F.prototype = this;
        newObj = new F();
        newObj.uber = this.prototype;
    
        for(I in overrides){  
            if(overrides.hasOwnProperty(i)){  
              newObj[i] = overrides[I];
            }  
        }
    
        return newObj;
    }
    

    第二种实现方法是列表形式,decorate方法会将装饰对象装到原始对象的decorate_list数组中,最终的getPrice方法会对所有的装饰对象的属性一起计算,这种方法不需要继承。

    代码如下:

    Function Sale (price) {  
        this.price = (price > 0) || 100;
        this.decorator_list = [];  
    }
    
    Sale.decorators = {};
    
    Sale.decorators.fedtax = function () {  
        getPrice: function (price) {  
          return price*2;
        }  
    }
    
    Sale.prototype.decorate = function (decorator) {  
      this.decorator_list.push(decorator);
    }
    
    Sale.prototype.getPrice = function () {  
      Var price = this.price,
              max = this.decorator_list.length,
              I;
    
      for(I = 0;i < max;I++){  
          name = this.decorator_list[I];
          price = this.constructors[name].getPrice(price);
        }
    
        return price;
    }
    展开全文
  • 装饰器模式 装饰器模式(AOP)是一种常见的设计模式,React框架中的高阶函数就是使用了装饰器模式,比如一个高阶函数接受了一个基础组件,这个基础组件设计之初只是一个单纯的展示组件,并没有任何的数据处理。现在...
  • 主要介绍了PHP、Python和Javascript装饰器模式对比,修饰模式(Decorator Pattern),又叫装饰者模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式,需要的朋友可以参考下
  • 装饰器模式介绍 为对象添加新的功能,不改变其原有的结构和功能,原有的功能还是可以使用,跟适配器模式不一样,适配器模式原有的已经不能使用了,装饰器示例比如手机壳 UML类图和代码示例 Circle示原来的...
  • // 装饰器模式: 允许向一个现有的对象添加新的功能,同时又不改变其结构 class Phone { // 原对象 call() { console.log('打电话'); } } class Decorator { // 新对象即新功能添加的装饰器 constructor ...
  • 装饰器模式 代理模式 观察者模式 策略模式 原型模式 桥接模式 组合模式 命令模式 享元模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型...
  • javascript设计模式之装饰器模式 js的设计模式分为创建型模式,结构型模式和行为模式结构模式描述了 如何组合对象以提供新的功能。装饰器模式是一种常见的结构型模式,我们可以以一个基础对象为基础,来给它加上若干...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 188
精华内容 75
关键字:

javascript装饰器模式

java 订阅