Decorator设计模式以及在js中的使用

936 阅读5分钟

Decorator Pattern 装饰器模式

互相认识一下

上次说到mixins模式在js中的应用,今天想要再说一说装饰器模式。

装饰器就是在不改变原有对象的基础上动态的添加一些额外的功能。这个定义让我联想到了Minecraft中的模组,游戏本身功能未发生任何改变,该出现的骷髅射手一个也不少,但是你的画面变得美丽清晰流畅了。装饰器就是这样的一个功能。

使用过ts的小伙伴对于装饰器一定不陌生,比如在vuejs中,使用ts:

@Prop() private visible: Boolean;

这个@Prop 就是装饰器模式的应用。

什么时候会使用装饰器模式呢?

场景一: 拓展对象的功能

如果上面的解释并没有让你理解什么是装饰器,那这么说吧 装饰器就是语法糖。简单又粗暴。

当我们使用了装饰器的时候,我们实际上是封装了一个操作对象的行为。就是使用的时候我们不知道也不需要知道装饰器内部是如何处理对象的。

如果以手机为例,我们实现一个通用的手机类。


class Mobile{
    constructor(model) {
        this.model = model;
    }
    
    getScreenSize(){
        return 5.5;
    }
    
    getCall(target) {
        console.log(`Calling ${target}.`) 
    }
}

那现在我有一个iphone,我发现这个iphone和mobile是有共同点的,但是也有不同点。iphone是mobile,但iphone 的屏幕尺寸不是5.5。这个时候我们就可以使用装饰器模式实现一个iphone。

需要注意的是,装饰器模式只能往一个已经存在的对象中添加或者修改某些属性,他不能更改对象的实现方式。

function getIphoneScreen(mobile) {
    mobile.getScreenSize = function() {
        return 5.8;
    }
}

const iphone11ProMax = new Mobile('iphone 11 pro max');
getIphoneScreen(iphone11ProMax);

console,log(iphone11ProMax.getScreenSize()); //5.8

在上面的例子中getIphoneScreen就是一个装饰器。他悄悄的按照我们的需求将一个通用的mobile的屏幕尺寸改为5.8。

当然还要另外一种实现模式就是实现一个子类iphone 11 pro max ,让他继承mobile,并重写getscreensize 方法。

function Iphone11ProMax(model) {
    Mobile.call(this, model);
    
    this.getScreenSize = function() {
        return 5.8;
    }
}

const myiphone11max = new Iphone11ProMax('iphone 11 pro max');

// es6 实现

class Iphone11ProMax extends Mobile {
    getScreenSize = function() {
        return 5.8;
    }
}

const myiphone11max = new Iphone11ProMax('iphone 11 pro max');

这两种都能实现功能,区别在于装饰器模式无需创建另一个通用的类,他只是在某个对象上进行了一些修改,他是作用在某个已知对象上的。

接下来我们拿一个更实际例子来说明装饰器的应用,在vue-property-decorator中,我们拿prop来举例:

export function Prop(options: PropOptions | Constructor[] | Constructor = {}) {
  return (target: Vue, key: string) => {
    applyMetadata(options, target, key)
    createDecorator((componentOptions, k) => {
      ;(componentOptions.props || ((componentOptions.props = {}) as any))[
        k
      ] = options
    })(target, key)
  }
}

createDecorator 方法创建了一个通用的装饰器,将传入的props注入这个通用的装饰器。我们并没有更改原来的props,也没有实现一个其他的prop类,而是通过注入的方式将从外部传入的prop 注入到组件的原生prop中。

注: createdecoratorhttps://class-component.vuejs.org/guide/custom-decorators.htmlvue-class-component的通用方法。

场景二: 临时需求

除了想改写某个已知对象的属性外,当我们想增加临时的功能的时候,也可以使用装饰器。

假设pm说哎呀这不是女神节了嘛,网站首页临时变成粉色的行不行。假设原来的网页是通过工厂模式创造出来的,此时如果你去更改构造器,把所有的颜色都改成粉色了,你可能要加班到12点。8号之后pm说 女神节过了,恢复原样吧。 你蒙了,以前的是啥颜色来着? 不慌不慌git有记录, 你又加班到12点。 连着加班,头发的根数又减少了是不是?TEAM LEADER 之前安排给你的活你又没时间做,绩效又gg了是不是?

如果用装饰器,一切可能就容易了一点。你只需要使用女神节装饰器,重写style,8号之后,你唯一需要做的就是移除这个装饰器。头发也不减少,该做的需求也完成了,team leader 觉得小伙子可以呀, 升职名单上就有你了是不是。除此之外你还可以有清明节装饰器,劳动节装饰器,圣诞节装饰器,美滋滋~

情景3: 调试代码

其实,在平时的时候,测试报了bug过来,我们觉得不应该是这样的呀,于是我们加了一行console.log(xxxx),这个行为其实就是应用了装饰器模式的思想。只不过我们没有给他抽象成一个方法。下面的伪代码就是将debug抽象成一个通用的方法。

function debugMethods(vm, methodName) {
    const originMethod = vm.methods[methodsName];
    console.log(originMethods());
    if(originMethods() !== '1') {
        console.log('error');
    }
}

const app = new Vue({
    data: {
        a: 1
    },
    methods:{
        consoleA() {
            console.log(this.a);
            return a+1;
        }
    }
})

debugMethods(app, consoleA);

mixins 模式 和 decorator 模式的区别

之前提过mixin模式,那 mixins 模式和装饰器有什么区别?

在js设计模式中,作者提到, mixins和装饰器都是将行为动态的添加到现有类中。但是装饰器模式增加的功能对于这个类来说并不是必须的,混入模式加入的功能就是从某些功能上来说是必须的。 所以混入模式有可能会造成原型污染和不确定性。装饰器模式更多的是专注于拓展功能的问题,向单个基础对象添加附加功能的装饰器对象,如果说装饰器模式是mc中的模组,或者lol中的皮肤,那mixins就是升级包,就是dlc。

总结

设计模式的奇妙就是从结果来看可能相似,但是内部的实现根据模式的不同而完全不同。,用性,拓展性也会不一样。

如果文章中有任何不当的地方欢迎在评论区指正。