装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
装饰器就是为了解决在不修改原本组件、接口或者类的时候为其添加额外的功能。从本质上看装饰器模式是一个包装模式((Wrapper Pattern),它是通过封装其他对象达到设计的目的。
为一个对象创建新的功能时,当然,我们可以用继承来实现。
class Shape {
constructor(name) {
this.name = name
}
draw() {
console.log(`draw ${this.name}`)
}
}
class ShapeColor extends Shape {
constructor(name) {
super(name)
}
setColor(color) {
console.log(`color the ${this.name} ${color}`)
}
}
const circle = new ShapeColor('triangle')
circle.setColor('blue')
circle.draw()
但是这样的方式会存在父类Shape和子类ShapeColor之间存在强耦合性的问题,一旦父类发生改变,那么子类也会随之发生改变,因此这种方式并不能灵活。
为了解决这些问题,我们需要装饰器模式,装饰器模式属于结构型的设计模式,其的特点是:
- 在不改变对象的基础上;
- 在程序运行期间给对象动态的添加职责;
class Shape {
constructor(name) {
this.name = name
}
draw() {
console.log(`draw ${this.name}`)
}
}
class ColorDecorator {
constructor(shape) {
this.shape = shape
}
draw() {
this.setColor()
this.shape.draw()
}
setColor() {
console.log(`color the ${this.shape.name}`)
}
}
let circle = new Shape('circle');
circle.draw();
let decorator = new ColorDecorator(circle);
decorator.draw();
通过ColorDecorator对象实现了一个相同的draw()方法,并在其中封装了setColor()这个额外的职责方法,用户同样在调用draw()方法时,也调用了上一个对象Shape的draw()方法。我们可以以此类推,给对象添加上色,设置边框的一系列职责。
实际上,装饰器也是一种包装器,把上个对象包装到某个对象中,层层包装。
SE7中的装饰器
ES7版本中引入了装饰器。
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world"; //直接添加到类中
target.prototype.eat = "apple"; //添加到类的原型对象中
}
var personOne = new Person();
console.log(Person['say']); // 'hello world'
console.log(personOne['eat']); // 'apple'
如果在class上添加一个装饰器,发现在浏览器中是无法直接运行的,如果要想使用它,还需要提前做一些准备工作,对它进行编译。
准备过程: 需要使用插件来让浏览器支持Decorator。
- 新建一个文件夹,然后安装插件:
npm i babel-plugin-transform-decorators-legacy babel-register --save-dev
- 创建文件 complie.js
const babel = require('babel-register');
babel({
plugins: ['transform-decorators-legacy']
});
require('./app.js');
3.创建app.js
@addSkill
class Person { }
function addSkill(target) {
target.say = "hello world"; //直接添加到类中
target.prototype.eat = "apple"; //添加到类的原型对象中
}
var personOne = new Person();
console.log(Person['say']); // 'hello world'
console.log(personOne['eat']); // 'apple'
- 执行compile.js 当然也可以用ts来写,效果是一样。
类装饰器
类修饰器是一个对类进行处理的函数,他的第一个参数target是装饰器要处理的目标类。
不带参数的类装饰器:
@addSkill
class Person { }
function addSkill(target) {
target.prototype.eat = "apple";
}
var personOne = new Person();
console.log(personOne.eat);
带参数的装饰器:
@addSkill('I love')
class Person { }
function addSkill(msg) {
return function(target){
target.prototype.eat = msg + ' ' + "apple";
}
}
var personOne = new Person();
console.log(personOne.eat);
问题: 装饰器在什么时候执行? 装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
方法装饰器
装饰器还可以用在类的某个方法上,成为方法装饰器。
class Person {
constructor(name){
this.name = name;
}
@MyName
getName(){
console.log(this.name);
return this.name;
}
}
function MyName(target, key, descriptor){
// console.log(target, key, descriptor);
const fn = descriptor.value;
descriptor.value = function(...args){
console.log(`${key} is called...` );
// fn && fn(...args);
fn && fn.apply(this, args);
}
}
let person = new Person('Tom');
person.getName();
参数说明:
target: 类的原型对象,上例是Person.prototype
key: 所要修饰的属性名 name
descriptor: 该属性的描述对象
@装饰器只能用于类和类的方法。 装饰器第一个参数是类的原型对象,上例是Person.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身);第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象。
多装饰器
如果同一个方法有多个装饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。
class Person {
constructor(name){
this.name = name;
}
@myName(1)
@myName(2)
getName(){
console.log(this.name);
return this.name;
}
}
function myName(msg) {
console.log('装饰器: ' + msg);
return (target, key, descriptor) => {
console.log('装饰器执行: ' + msg);
}
}
let person = new Person('Tom');
person.getName();
遵循洋葱圈模型,从外向里-进入,然后从内向外-执行。
结果:
装饰器: 1
装饰器: 2
装饰器执行: 2
装饰器执行: 1
Tom
装饰器不能作用于函数
装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
实际执行相当于:
var counter;
var add;
@add
function foo() {
}
counter = 0;
add = function () {
counter++;
};
如果一定要装饰函数的话,可以采用高阶函数的形式来直接执行。
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
Angular中的装饰器
Angular中的装饰器,功能非常强大,有系统级的Component,Module,Input等装饰器,还可以自定义装饰器 class decorator, method decorator, property decorator。而且自定义的装饰器,不只可以装饰类和类方法,可以装饰类的属性。
export function CLog(): ClassDecorator {
// tslint:disable-next-line:only-arrow-functions
return function(constructor: any) {
const visit = constructor.prototype.visit;
constructor.prototype.visit = function(...args) {
console.log(this.title + ' visit...');
visit.apply(this, ...args);
};
};
}
export function MLog(): MethodDecorator {
return (instance, propertyKey: string, descriptor: any) => {
console.log(instance, descriptor);
const fn = descriptor.value;
descriptor.value = (...args) => {
console.log(`${propertyKey} is called.`);
fn.apply(instance, ...args);
};
};
}
export function VLog(instance, propertyKey) {
let value = instance[propertyKey];
Object.defineProperty(instance, propertyKey, {
get(){
return value;
},
set(newVal){
if (newVal > 5) {
throw new Error('cannot be greater than 6.');
}
else {
value = newVal;
}
}
});
}
import { Component, OnInit } from '@angular/core';
import { CLog } from './decorators/clog';
import { MLog } from './decorators/mlog';
import { VLog } from './decorators/vlog';
// https://www.cnblogs.com/oxspirt/p/14362644.html
@Component({
selector: 'app-dec-comp',
templateUrl: './decorator-comp.component.html'
})
@CLog()
export class DecoratorComponent implements OnInit {
title = 'my-component';
@VLog count = 0;
constructor(){
//
}
ngOnInit(): void {
// console.log('my ngOnInit');
this.visit();
}
visit(): void {
console.log('visit...');
}
@MLog()
add(){
this.count++;
}
}
注意:
Angular中的装饰器和ES7中的装饰器有些不同:
- ES7中的class装饰器第一个参数是class本身,类方法装饰器第一个参数是class的prototype;
- Angular的类方法装饰器和属性装饰器的第一个参数都是类实例;
- Angular比ES7多了属性装饰器;
总结:
Angular中的装饰器功能更加强大。
React中的装饰器模式
为了提高组件复⽤用率,可测试性,就要保证组件功能单一性;但是若要满⾜足复杂需求就要扩展功能单一的组件,在React里就有了HOC(Higher-Order Components)的概念。 高阶组件时参数为组件,返回值为新组件的函数。
import React, { Component, PureComponent } from 'react'
function HOCTest(Comp){
return function(props){
return (<div style={{ border: '1px solid green'}}>
<Comp {...props}/>
</div>);
}
}
export default HOCTest(function CachePage(props){
return (<div>
Tom
</div>);
})
HOC通常实现为一个函数,本质上是抽象工厂模式。 高阶组件本身是对装饰器模式的应用,自然可以利用ES7中出现的装饰器语法来更优雅的书写代码。
@HOCTest
function CachePage(props){
return (<div>
Tom
</div>);
}
优点缺点
优点
装饰类和被装饰类可以独立发展,不会相互耦合; 装饰模式是继承的一个替代模式; 装饰模式可以动态扩展一个实现类的功能,而不必担心影响实现类;
缺点
如果管理不当会极大增加系统复杂度; 多层装饰比较复杂;
设计原则
装饰器模式将现有对象和装饰器进行分离,两者独立存在,符合开放封闭原则。
我的微信公众号
更多精彩文章请关注我的前端技术公众号哦!