Шаблон декоратора ES7 Decorator

внешний интерфейс Шаблоны проектирования
Шаблон декоратора ES7 Decorator

ES7 Decorator 装饰者模式

1, декоративные узоры

Все знают о шаблонах проектирования, и в Интернете есть множество обучающих материалов.

просто поделитесь здесьшаблон декоратораdecoratorконцепция.

1.1. Декоративный узор в сравнении с шаблоном-адаптером

И шаблон декоратора, и шаблон адаптерарежим упаковки(Wrapper Pattern), все они достигают цели дизайна, инкапсулируя другие объекты, но их формы очень разные.

  • режим адаптераМы используем много сценариев, например, подключение к разным базам данных, вам нужно обернуть интерфейс существующего модуля, чтобы он мог адаптироваться к базе данных — так же, как ваш мобильный телефон использует адаптер для адаптации к сокету;

  • Декоративный рисунокОтличие, просто упаковка существующего модуля, чтобы сделать его «более великолепным», не повлияет на функцию исходного интерфейса - так же, как добавление корпуса к мобильному телефону, это не повлияет на исходные функции мобильного телефона, такие как вызов и зарядка. ;
    更加华丽

Дополнительные отличия см.Шаблоны проектирования — Декоратор

1.2 Сцена в режиме оформления — АОП-ориентированное программирование

Классическим применением режима украшения является программирование АОП, такое как "система журналов". Функция системы журналов состоит в том, чтобы записывать поведение системы. Он добавляет ссылку на запись, не влияя на функции исходной системы - это как носить умную руку.Кольцо не влияет на вашу повседневную работу и отдых, но теперь у вас есть свои ежедневные записи поведения.

Более абстрактное понимание можно понимать как слой для потока данных.filter,因此 AOP 的典型应用包括 安全检查、缓存、调试、持久化等等。 Может относиться кПринцип Spring aop и различные сценарии применения.
aop

2. Используйте декораторы ES7

ES7 добавилdecoratorатрибут, заимствованный из Python, см. статьюDecorators in ES7.

Ниже мы беремЖелезный человекПример, объясняющий, как использовать декораторы ES7.

Взяв за пример Железного Человека, Железный Человек по сути является человеком, но только после того, как "украсил" много оружия, он стал таким NB, но как бы он ни был украшен, он все равно остается человеком.
钢铁侠

Наш пример сценария выглядит так

  • Сначала создайте нормальныйManКласс, значение его сопротивления 2, силы атаки 3 и HP 3;
  • Затем вкладываем в броню Железного человека, чтобы его сопротивление увеличилось на 100 до 102;
  • Пусть он принесет светлые перчатки, мощность атаки увеличилась на 50, становится 53;
  • Наконец, пусть он добавит «рейс» возможности

ironman

2.1. [Демонстрация 1] Украсьте метод: наденьте доспехи

Создайте класс «Человек».:

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  toString(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}

var tony = new Man();

console.log(`当前状态 ===> ${tony}`); 

// 输出:当前状态 ===> 防御力:2,攻击力:3,血量:3

код прямо вbabeljs.io/repl/Запустите, чтобы просмотреть результаты, не забудьте проверитьExperimentalОпции иEvaluateОпции

СоздайтеdecorateArmourМетод сборки брони для Железного человека - ПримечаниеdecorateArmourоформлен в методеinitВверх.

function decorateArmour(target, key, descriptor) {
  const method = descriptor.value;
  let moreDef = 100;
  let ret;
  descriptor.value = (...args)=>{
    args[0] += moreDef;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  toString(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}

var tony = new Man();

console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3

Посмотрим сначала на результат, защита действительно увеличилась100, вроде броня сработала.

У новичков тут два вопроса:

    1. decorateArmourПочему параметры метода именно эти три? Можно ли его заменить?
    1. decorateArmourПочему метод возвращаетdescriptor

Вот личный ответ для справки:

  1. DecoratorsСуть в том, чтобы воспользоваться преимуществами ES5Object.definePropertyсвойства, эти три параметра фактически иObject.definePropertyПараметры одинаковые, поэтому изменить их нельзя.Для подробного анализа см.Работа над декораторами JavaScript ES7
  2. может видетьПосле вавилонского преобразованиякод, один из которыхdescriptor = decorator(target, key, descriptor) || descriptor;, пока что я не буду здесь его подробно раскрывать, контекст этой строки кода вы можете посмотреть сами (объяснение этого кода также задействовано в справке).

2.2. [Демонстрация 2] Наложение декоратора: добавление лучевых перчаток

В примере выше мы увеличиваем успех для обычных людей "доспехи" украшаем это, теперь я хочу дать ему увеличение "легких перчаток" в надежде на дополнительные50Точечная защита.

Step 1: копияdecorateArmourметод, переименованныйdecorateLight, при изменении свойств значения защиты:

function decorateLight(target, key, descriptor) {
  const method = descriptor.value;
  let moreAtk = 50;
  let ret;
  descriptor.value = (...args)=>{
    args[1] += moreAtk;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

Step 2: прямо вinitДобавьте синтаксис оформления в метод:

....
  @decorateArmour
  @decorateLight
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
 ...

Окончательный код выглядит следующим образом:

...
function decorateLight(target, key, descriptor) {
  const method = descriptor.value;
  let moreAtk = 50;
  let ret;
  descriptor.value = (...args)=>{
    args[1] += moreAtk;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  @decorateLight
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
...
}
var tony = new Man();
console.log(`当前状态 ===> ${tony}`);
//输出:当前状态 ===> 防御力:102,攻击力:53,血量:3

Здесь вы можете увидеть преимущества режима украшения, его можно использовать для наложения метода, и он очень навязчив по отношению к исходному классу, он просто добавляет строку@decorateLightВот и все, его можно легко добавлять и удалять; (а также можно использовать повторно)

2.3, [Демонстрация 3] Классовое украшение: увеличивает способность к полету

По статьеорнаментТем не менее, есть два режима украшения:чистый декоративный узора такжеПолупрозрачный декоративный узор.

Две демонстрации выше должны использоватьчистый декоративный узор, он не увеличивает интерфейс к исходному классу, демо состоит в том, чтобы добавить возможность "полетать" обычным людям, что равносильно добавлению метода в класс, который принадлежитПолупрозрачный декоративный узор, немного похоже на шаблон адаптера.

Step 1: добавить метод:

function addFly(canFly){
  return function(target){
    target.canFly = canFly;
    let extra = canFly ? '(技能加成:飞行能力)' : '';
    let method = target.prototype.toString;
    target.prototype.toString = (...args)=>{
      return method.apply(target.prototype,args) + extra;
    }
    return target;
  }
}

Step 2: этот метод перейдет непосредственно к декорированному классу:


...

// 3
function addFly(canFly){
  return function(target){
    target.canFly = canFly;
    let extra = canFly ? '(技能加成:飞行能力)' : '';
    let method = target.prototype.toString;
    target.prototype.toString = (...args)=>{
      return method.apply(target.prototype,args) + extra;
    }
    return target;
  }
}

@addFly(true)
class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  @decorateLight
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  ...
}
...

console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:53,血量:3(技能加成:飞行能力)

действует по методуdecoratorПервый полученный параметр (target) это классprototype; если поставитьdecoratorПрименительно к классу его первый параметр target равенсам класс. (Ссылаться наDecorators in ES7)

3, реализован с использованием родного декоратора JS

Чтобы узнать, как реализовать шаблон оформления с помощью существующего стандартного нативного JS, обратитесь к переводу.
Шаблоны проектирования JavaScript: шаблон декоратора, это достойная статья для прочтения.

Здесь мы переписываем сцену Демо 1 выше с ES5 и кратко говорим о ключевых моментах:

  1. Человек — это конкретный класс,Decoratorнаправлен наManБазовый класс декоратора
  2. конкретный класс отделкиDecorateArmourобычно используетсянаследование прототипаунаследовано отDecoratorбазовый класс;
  3. на основеИдея IOC (инверсия управления),Decoratorэто принятьManкласс вместо того, чтобы создавать свой собственныйManДобрый;

// 首先我们要创建一个基类
function Man(){

  this.def = 2;
  this.atk = 3; 
  this.hp = 3;
}

// 装饰者也需要实现这些方法,遵守 Man 的接口
Man.prototype={
  toString:function(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}
// 创建装饰器,接收 Man 对象作为参数。
var Decorator = function(man){
  this.man = man;
}

// 装饰者要实现这些相同的方法
Decorator.prototype.toString = function(){
    return this.man.toString();
}

// 继承自装饰器对象
// 创建具体的装饰器,也是接收 Man 作对参数
var DecorateArmour = function(man){

  var moreDef = 100;
  man.def += moreDef;
  Decorator.call(this,man);

}
DecorateArmour.prototype = new Decorator();

// 接下来我们要为每一个功能创建一个装饰者对象,重写父级方法,添加我们想要的功能。
DecorateArmour.prototype.toString = function(){
  return this.man.toString();
} 

// 注意这里的调用方式
// 构造器相当于“过滤器”,面向切面的
var tony = new Man();
tony = new DecorateArmour(tony);
console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3

4. Классическая реализация: регистратор

Классическим применением АОП являетсялог-системаЧто ж, давайте также воспользуемся синтаксисом ES7 для создания системы логов для Iron Man.

logger

Вот окончательный код:

/**
 * Created by jscon on 15/10/16.
 */
let log = (type) => {

  return (target, name, descriptor) => {
    const method = descriptor.value;
    descriptor.value =  (...args) => {
      console.info(`(${type}) 正在执行: ${name}(${args}) = ?`);
      let ret;
      try {
        ret = method.apply(target, args);
        console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`);
      } catch (error) {
        console.error(`(${type}) 失败: ${name}(${args}) => ${error}`);
      }
      return ret;
    }
  }
}
class IronMan {
  @log('IronMan 自检阶段')
  check(){
    return '检查完毕';
  }
  @log('IronMan 攻击阶段')
  attack(){
    return '击倒敌人';
  }
  @log('IronMan 机体报错')
  error(){
    throw 'Something is wrong!';
  }
}

var tony = new IronMan();
tony.check();
tony.attack();
tony.error();

// 输出:
// (IronMan 自检阶段) 正在执行: check() = ?
// (IronMan 自检阶段) 成功 : check() => 检查完毕
// (IronMan 攻击阶段) 正在执行: attack() = ?
// (IronMan 攻击阶段) 成功 : attack() => 击倒敌人
// (IronMan 机体报错) 正在执行: error() = ?
// (IronMan 机体报错) 失败: error() => Something is wrong!

LoggerКлюч к методу:

  • первое использованиеconst method = descriptor.value;Извлеките исходный метод, чтобы обеспечить чистоту исходного метода;
  • существуетtry..catchоператор звонитret = method.apply(target, args);Лог-отчеты до и после звонка;
  • последнее возвращениеreturn ret;исходный результат звонка

Я считаю, что этот набор идей послужит нам хорошей отправной точкой для реализации модели АОП в будущем.

5. Расширение: на основе заводского шаблона

Если вам нужен Железный человек с 3 функциями, вам нужно использоватьновый операторСоздайте 4 объекта. Это утомительно и раздражает, поэтому мы сможем создать Железного Человека со всеми функциями всего одним вызовом метода.

Это требуетзаводской узорОфициальное определение фабричного режима: создание экземпляра объекта-члена класса в подклассе. Например, определениеукраситьIronMan(человек, особенность)метод, который принимаетPersonОбъект (вместо того, чтобы инициализировать себя), что эквивалентно конвейерному производству.

factory

какОбъединение шаблона декоратора и шаблона фабрикиУлучшите производительность кода, этот отличный переводШаблоны проектирования JavaScript: фабричный шаблонДан подробный метод, который не будет повторяться здесь. Настоятельно рекомендуется прочитать эту статью.

6. Хотите использовать его сейчас?

decoratorПока это всего лишь предложение, но благодаря Babel мы можем испытать его прямо сейчас. Сначала установитеbabel:

npm install babel -g

Затем включитеdecorator:

babel --optional es7.decorators foo.js > foo.es5.js

Babel также предоставляетОнлайн замена, чек об оплатеэкспериментальный вариант, Это оно.

Настройка babel в webstorm

Step 1: сначала установить глобальноbabelкомпонентный модуль

npm install -g babel

Step 2: Установите объем (этот шаг может быть опущен)

设置

Назовите область:

命名 scope

Добавить файл в текущую область:
add

Step 3: установить версию ES

set

Step 4: Добавить наблюдатель

watcher

аргументы могут быть заполнены:$FilePathRelativeToProjectRoot$ --stage --out-file $FileNameWithoutExtension$-es5.js $FilePath$

.

Если вам нужна исходная карта, вам нужно добавить--source-mapвариант, покаOutput paths to refreshЗаполнить$FileNameWithoutExtension$-es5.js:$FileNameWithoutExtension$-es5.js.map

Справочник по дополнительным настройкамbabel cli

7. Резюме

Хотя это характерно для ES7, но сегодня популярная тенденция Babel, мы можем воспользоваться Babel, чтобы использовать его. Мы можем использовать инструмент командной строки Babel или подключаемый модуль grunt, gulp, webpack of babel для использования декораторов.

Приведенный выше код можно поместить непосредственно вbabeljs.io/repl/Запустите, чтобы просмотреть результаты;

Для более интересного игрового процесса декораторов ES7 вы можете обратиться к часто используемым, реализованным Niuren.Декораторы: основные декораторы. и РаганвальдаКак использовать декораторы для реализации миксинов.

использованная литература