Декораторы

Разбираемся что это и в чем отличия

Часть 2. Оператор декорирования

Из предыдущей части мы поняли, что декоратор — это удобный способ изменения поведения некоторой функции или класса, или объекта. Для того, чтобы декораторами можно было удобно пользоваться, в некоторых языках программирования (например Python или Java*) есть специальные операторы. Javascript не исключение. Данный оператор есть в стандарте ES7 , по синтаксису он похож.

Основная задача декоратора — это расширение функциональности объекта, не используя наследования. Следовательно декораторы необходимо использовать, когда требуется реализовать много независимых расширений и не создавать множество подклассов, что может привести к большой сложности системы.

Синтаксис декораторов пока находится в экспериментальных стадиях. В Babeljs их можно включить так:

$ npm install babel-plugin-transform-decorators
Usage:
{
"plugins": ["transform-decorators"]
}

В TypeScript для этого требуется аргумент (или запись в tsconfig.json)

--experimentalDecorators

С точки зрения синтаксиса выглядит все достаточно просто. Каждый вызов декоратора начинается с символа @. Перепишем наш пример с расчетом стоимости кофе, в зависимости от ингредиентов, с применением синтаксиса ES7 декораторов:

Если не видеть реализацию декораторов, а пользоваться только результатом инкапсуляции, то выходит весьма красиво (и немного магически, если не знать как работает данный механизм).

@withMilk
@withWhip
@withSprinkles

class Coffee {
private cost :number = 1;
getCost() :number {
return this.cost;
}
}
var myCoffee = new Coffee();
console.log( myCoffee.getCost() );

Мы, конечно, могли бы сделать тоже самое через наследование, но представьте себе в какой код превратился бы наш пример. Мы могли бы использовать паттерн Композиция, как альтернатива… Но декораторы добавляют новый уровень метапрограммирования, делая код более понятным для чтения. Не таким простым, но зато легким для чтения.

Если бы нам пришлось писать тот же код на ES5, используя функцию декорирования, то это выглядело бы так:

В TypeScript уже описаны сигнатуры различных декораторов:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

На основании этих сигнатур TypeScript генерит различные сообщения об ошибках.

Исходя из этих описаний можно понять где могут быть использованы декораторы и аннотации. Они могут быть использованы для:

  • класса
  • свойства
  • метода
  • аргумента

Аннотации

Аннотации — способ добавления метаданных к объявлению класса для использования во внедрении зависимостей или директивах компилятора.

Аннотации были предложены командой разработки языка AtScript из Google, но они не входят в стандарт. Декораторы были предложены в качестве стандарта в ES7 для изменения классов и свойств на этапе разработки.

С точки зрения разработчика у аннотаций и декораторов абсолютно одинаковый синтаксис. Отличие в том, что разработчик не контролирует то, как аннотации добавляют метаданные в код.

Декораторы можно рассматривать как интерфейс для построения чего-то, что ведет себя как аннотации.

Давайте разбираться по порядку. Сначала научимся работать с декораторами и затем плавно перейдем к работе с аннотациями.