资讯专栏INFORMATION COLUMN

从装饰模式到装饰器

monw3c / 2663人阅读

摘要:从装饰模式到装饰器装饰模式装饰模式的作用是在不修改原有的接口的情况下,让类表现的更好。它是一个语法糖说完了装饰模式,我们再看一下在中最新引入的装饰器。

从装饰模式到装饰器 装饰模式

装饰模式的作用是:在不修改原有的接口的情况下,让类表现的更好。
什么叫更好?

为什么需要装饰模式

自然是继承有一些问题
继承会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变;
超类的内部细节对于子类是可见的,继承常常被认为破坏了封装性;

js中的装饰模式

js中的装饰模式实际上把一个对象A嵌入到另外一个对象B中,也就是B对象包装了A对象,形成了一个包装链。请求随着这一条包装链一次传递到了包装链上的所有对象,每一个对象都有机会处理这一个请求。

设想一下场景,打农药,我选了一个后羿,然后开始一级我学了第一个技能。

var HouYi = {
    skill : function() {
        console.log("增加了攻速")
    }
    HP :function(){
        return 1999
    }
}

HouYi.skill()

结果,自以为自己很6了的后羿去了下路,遇到了狄仁杰,二马一错蹬几个回合下来就后羿就凉了。后羿觉得不服,回家一趟,发奋努力,又学了第二个技能和第三个技能。

class Hero {
    buy(){
        console.log("有了一双鞋子")
        // console.log("有了一件复活甲")
        // console.log("有了一把饮血剑")
    }
}


var Houyi = new Hero()
Houyi.buy()

那么问题来了,我们看了一下,后羿还是回家买了新的武器装备,而不是寒酸的只有一双鞋。但是,我们看到,每次后羿买东西都要回家,也就是都要修改buy方法,那么怎么样在不回家,不修改buy的方法的基础上又把东西卖了呢,也就是,如何动态的买东西。

从英雄联盟过渡到王者荣耀。

也就是在代码运行期间,我们很难切入某个函数的执行环境。

class Hero {
    buyShoes(){
        console.log("有了一双鞋子")
    }
}

var Houyi = new Hero()

var buyAtk = function() {
    console.log("有了一把饮血剑")
}

var buyDef = function () {
    console.log("有了一件复活甲")
}

var buyShoes= Houyi.buy

Houyi.buybuybuy = function() {
    buyShoes()
    buyAtk()
    buyDef()
}

Houyi.buybuybuy()

总结一下:装饰模式是为已有功能动态地添加更多功能的一种方式,把每个要装饰的功能放在多带带的函数里,然后用该函数包装所要装饰的已有函数对象,因此,当需要执行特殊行为的时候,调用代码就可以根据需要有选择地、按顺序地使用装饰功能来包装对象。优点是把类(函数)的核心职责和装饰功能区分开了。

装饰模式的缺点:缺点的话我们也能看到我们定义了很多很相似的细小对象到我们的命名空间中,这样使我们的架构变得十分的复杂,穷于管理。这就有可能导致,我们不是使用它而是被它使用。

它是一个语法糖

说完了装饰模式,我们再看一下在ES7中最新引入的装饰器(decorator)。这个概念其实是从python里引进的。

def my_decorator(fn):
  def inner(name):
    print "Hello " + fn(name)
  return inner

@my_decorator
def greet(name):
  return name

greet("Decorator!")
# Hello Decorator!

这种@decorator的写法其实就是一个语法糖。

语法糖意指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过其并没有给语言添加什么新东西。

所以,ES7中的 decorator 同样借鉴了这个语法糖,不过依赖于ES5的Object.defineProperty 方法 。

defineProperty 所做的事情就是,为一个对象增加新的属性,或者更改对象某个已存在的属性。调用方式是 Object.defineProperty(obj, prop, descriptor) ,这 3 个参数分别代表:

obj: 目标对象

prop: 属性名

descriptor: 针对该属性的描述符

关于descriptor代表的意思是对象描述符,它本身一个对象,用于描述目标对象的一个属性的属性。

value:属性的值,默认为undefined

writable:能否修改属性的值,默认值为true

enumerable:能否通过for-in循环返回属性。默认为ture

configurable:能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为访问器属性,默认为true.

关于最后一个configurable还要多说一句,当我们把对象的一个属性设置为false之后,我们就不能再二次修改了,也就是不能置成true,会报错。

const object1 = {};

Object.defineProperty(object1, "property1", {
  value: 42,
  writable: false,
  configurable:false
});

object1.property1 = 77;
// throws an error in strict mode

console.log(object1.property1);
// expected output: 42
装饰类

decorator大量用于装饰ES6中的类。具体的写法是:

@decoratorFun
class Base{}

function decoratorFun(target){
    target.bool = true
}

Base.bool  //   true

上面的代码,decorator就是一个装饰器,它装饰了Base这个类的行为(为其增加了一个静态属性)。而函数的target属性,就是被装饰的类本身。

所以,就是说,如果装饰器是一个对类做修改装饰的一般函数,那么他的第一个参数就是被装饰类的引用。但是,如果一个参数不够用,怎么办?

如果不够用,那么直接可以在外面再包一层。

function decorFun2(str){
    return function(target){
        target.name = str
    }
}

@decorFun2("zhangjingwei")
class Person {}
Person.name //   zhangjingwei

@decorFun2("susu")
class Person2 {}
Person2.name     // susu
class Foo1 {  
     classMethod() {  
        return "hello";  
    }  
} 
class Foo2 {  
     static classMethod() {  
        return "hello";  
    }  
} 

Foo1.classMethod()
var foo1 = new Foo1();  
foo1.classMethod() 

Foo2.classMethod()
var foo2 = new Foo2()
foo2.classMethod()
静态属性和实例属性。静态属性是类本身的属性,类生成的对象不能继承该属性,但是实例属性是类生成的对象可以继承的。   
ES6的规范说,类里面没有静态属性,只有静态方法。

上面的都是给类添加静态属性,如果想要增加实例属性,那么可以操作类的原型。

function decorFun3(name){
    return function(target) {
        target.prototype.name = name
    }
}

@decorFun3("lubanqihao")
class Nongyao {}

let ny1 = new Nongyao()
ny1.name    // lubanqihao
装饰类的属性

装饰器不仅可以装饰类,还能装饰类的方法。

有的时候,我们想把类中的某个属性设置成只读不支持修改,可以来用装饰器来实现。

function readonly(target,name,descriptr){
    descriptor.writable = false
    return descriptor
}

class Cat{
    @readonly
    say(){
        console.log("miaomiao)
    }
}

let kitty = new Cat()
kitty.say = function(){
    console.log("wangwang")
}

kitty.say()      //miaomiao

我们看到通过装饰器给类中的say方法,设置成了只读。

参数有三个,target name descriptor。

第一个参数是类的原型对象,装饰器的本意是装饰类的实例,但是因为类的实例还没有生成,只能去修饰类的原型。第二个参数是要修饰的属性名。第三个参数是该属性的描述对象。

这个很眼熟是不是?

是不是有点类似于Object.defineProperty().其实装饰器对类的属性的作用,就是通过Object.defineProperty这个方法进行扩展和封装。

实际上装饰器的行为原理是这样的:

let descriptor = {
    value:function(){
        console.log("miaomiao)
    },
    enumerable:false,
    configable:true,
    writable:true
}

descriptor = readonly(Cat.protitype,"say",descriptor) || descriptor

Object.defineProperty(Cat.prototype, "say",descriptor);

所以,我们看到,当装饰器操作类本身的时候,操作的对象也是类本身,但装饰器操作的是类中的方法的时候,操作的对象是是类的描述符descriptor,因为类中的属性的全部信息都记录在这个描述符里面。

装饰器不能修饰方法,是因为存在变量提升,不会生效

core-decorators.js第三方模块

core-decorators.js是一个第三方模块,提供了一些常用的装饰器。

@autobind:这个装饰器的作用是自动绑定this对象。

import {autobind} from "core-decorators";

class Person {
    @autobind
    getPerson() {
        return this
    }
}

let p1 = new Person()
let getPerson = p1.getPerson()

getPerson() === p1     // true

@readonly 修饰方法使方法变得不可写

import {readonly} from "core-decorators"

class Dog {
    @readonly
    name = "zhang"
}

let d1 = new Dog()
d1.name = "susu"    // 会报错,因为属性不可写

override 会检查子类的方法是否覆盖了父类的同名方法。如果不正确,会报错。

import {override} from "core-decorators"

class Parent {
  speak(first, second) {}
}

class Child extends Parent {
  @override
  speak() {}
  // SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
}

// or

class Child extends Parent {
  @override
  speaks() {}
  // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain.
  //
  //   Did you mean "speak"?
}
Mixin混入

意思就是在一个对象中混入另外一个对象的方法,是对象继承的一种替代方式。

//一个混入的简单实现:

class Foo {
    foo() {
        console.log("hahaha")
    }
}

class Bar {}

Object.assign(Bar.prototype,Foo)
let bar1 = new Bar()

bar1.foo()

上边的例子是通过Object.assign方法进行的。Bar的实例都有foo方法。

所以呢,我们可以把混入的方法多带带拿出来,结合装饰器使用。

// mixin.js

export function mixin(...list){
    return function (target) {
        Object.assign(target.prototype,...list)
    }
}
import {mixin} from "./mixin.js

class Foo {
    foo() {
        console.log("lalala")
    }
}

@mixin(Foo)
class NewFoo {}

let nf = new NewFoo()
nf.foo()

这样一个不好的地方,就是他会改写新类的原型对象。

Babel转码

需要安装下边的东西:

npm install --save-dev babel-core babel-preset-stage-0

然后设置文件.babelrc

{
  "plugins": ["transform-decorators"]
}

这样,就能在代码里实现装饰器了。

参考资料https://github.com/zhiqiang21...
http://www.liuhaihua.cn/archi...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90411.html

相关文章

  • 聊聊Typescript中的设计模式——装饰篇(decorators)

    摘要:本文从装饰模式出发,聊聊中的装饰器和注解。该函数的函数名。不提供元数据的支持。中的元数据操作可以通过包来实现对于元数据的操作。   随着Typescript的普及,在KOA2和nestjs等nodejs框架中经常看到类似于java spring中注解的写法。本文从装饰模式出发,聊聊Typescipt中的装饰器和注解。 什么是装饰者模式 Typescript中的装饰器 Typescr...

    yiliang 评论0 收藏0
  • ES6重新认识JavaScript设计模式: 装饰模式

    摘要:什么是装饰器模式向一个现有的对象添加新的功能,同时又不改变其结构的设计模式被称为装饰器模式,它是作为现有的类的一个包装。中的装饰器模式中有一个的提案,使用一个以开头的函数对中的及其属性方法进行修饰。 1 什么是装饰器模式 showImg(https://segmentfault.com/img/remote/1460000015970102?w=1127&h=563); 向一个现有的对...

    wendux 评论0 收藏0
  • 包装模式就是这么简单啦

    摘要:包装模式是这样干的首先我们弄一个装饰器,它实现了接口,以组合的方式接收我们的默认实现类。其实装饰器抽象类的作用就是代理核心的功能还是由最简单的实现类来做,只不过在扩展的时候可以添加一些没有的功能而已。 前言 只有光头才能变强 回顾前面: 给女朋友讲解什么是代理模式 前一篇已经讲解了代理模式了,今天要讲解的就是装饰模式啦~ 在看到FilterInputStream和FilterOutpu...

    Developer 评论0 收藏0
  • 【用故事解读 MobX 源码(四)】装饰 和 Enhancer

    摘要:所以这是一篇插队的文章,用于去理解中的装饰器和概念。因此,该的作用就是根据入参返回具体的描述符。其次局部来看,装饰器具体应用表达式是,其函数签名和是一模一样。等装饰器语法,是和直接使用是等效等价的。 ================前言=================== 初衷:以系列故事的方式展现 MobX 源码逻辑,尽可能以易懂的方式讲解源码; 本系列文章: 《【用故事解...

    maybe_009 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<