Вы находитесь на странице: 1из 17

METANIT.

COM
Сайт о программировании

                    

Декораторы
Последнее обновление: 20.05.2021

            

Yandex Games

Bubble Hit

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

Декораторы представляют функции, которые могут применяться к классам, методам,
методом доступа (геттерам и сеттерам), свойствам, параметрам.

На текущий момент декораторы являются экпериментальной функциональностью языка
TypeScript, поэтому при компиляции следует указывать параметр experimentalDecorators.
Например, через файл tsconfig.json:

1 {
2     "compilerOptions": {
3         "target": "ES5",
4         "experimentalDecorators": true
5     }
6 }

Либо через параметры в командной строке:

tsc app.ts ­t ES5 ­­experimentalDecorators
Декораторы классов
Декоратор класса применяется к конструктору класса и позволяет изменять или заменять
определение класса.

Декоратор класса представляет функцию, которая принимает один параметр:

1 function classDecoratorFn(constructor: Function){ }

В качестве параметра выступает конструктор класса. Например, определим простейший
декоратор:

1 function sealed(constructor: Function) {
2     console.log("sealed decorator");
3     Object.seal(constructor);
4     Object.seal(constructor.prototype);
5 }
6  
7 @sealed
8 class User {
9     name: string;
10     constructor(name: string){
11         this.name = name;
12     }
13     print():void{
14         console.log(this.name);
15     }
16 }

Декоратор sealed с помощью функции Object.seal запрещает расширение прототипа класса
User.

Для применения декоратора используется знак @. Сам декоратор ставится перед
названием класса. То есть из­за применения декоратора мы, к примеру, не сможем
добавить в класс User новое свойство следующим образом:

1 Object.defineProperty(User, 'age', {
2     value: 17
3 });

Также декораторы могут изменять результат работы конструктора. В этом случае
определение функции декоратора немного меняется, но она также в качестве параметра
принимает конструктор класса:

1 function logger<TFunction extends Function>(target: TFunction): TFunction{
1 function logger<TFunction extends Function>(target: TFunction): TFunction{
2  
3     let newConstructor: Function = function(name:string){
4         console.log("Creating new instance");
5         this.name = name;
6         this.age = 23;
7         this.print = function():void{
8             console.log(this.name, this.age);
9         }
10     }
11     return <TFunction>newConstructor;
12 }
13  
14 @logger
15 class User {
16     name: string;
17     constructor(name: string){
18         this.name = name;
19     }
20     print():void{
21         console.log(this.name);
22     }
23 }
24 let tom = new User("Tom");
25 let bob = new User("Bob");
26 tom.print();
27 bob.print();

В данном случае декоратор logger типизирован типом TFunction, который является
расширением типа Function, то есть функции. По сути это тип функции конструктора.

В самом декораторе передаваемый конструктор target никак не используется. Но создается
новый конструктор. Мы предполагаем, что в конструктор будет передаваться некоторый
параметр, который будет называться name. Значение этого параметра передается свойству
this.name = name;. Также в конструкторе устанавливается новое свойство this.age и
метод this.print(), который выводит на консоль значения обоих свойств.

Далее декоратор применяется к классу User. У этого класса определен конструктор,
который устанавливает свойство name. Однако поскольку мы переопределили конструктор,
то в реальности при создании объекта User будет устанавливаться как свойство name, так
и свойство age. И, кроме того, будет переопределяться метод print.

Вывод консоли браузера

Creating new instance Creating new instance Tom 23 Bob 23

Следует учитывать, что замена конструктора приводит к полной замене всех свойств и
методов класса.
METANIT.COM
Сайт о программировании

                    

Декораторы методов и их параметров
Последнее обновление: 20.05.2021

            

Yandex Games
Solitaire ­
Yandex
Games

Декоратор метода

Декоратор метода также представляет функцию, которая принимает три параметра:

1 function deprecated(target: any, propertyName: string, descriptor: PropertyDescriptor){ 
2     console.log("Method is deprecated");
3 }

Декоратор принимает следующие параметры:

1. Функция конструктора класса для статического метода, либо прототип класса для
обычного метода.

2. Название метода.

3. Объект интерфейса PropertyDescriptor:

1 interface PropertyDescriptor{
2     configurable?: boolean;
3     enumerable?: boolean;
4     value?: any;
5     writable?: boolean;
5     writable?: boolean;
6     get? (): any;
7     set? (v: any): void;
8 }

Этот объект описывает изменение декорируемого метода. Применяется при
компиляции в ES5 и выше, при ES3 имеет значение undefined.

Его свойство value содержит определение функции. Свойство writable указывает,
является ли функция модифицируемой (если значение true, то является).

Определим простейший декоратор для метода:

1 function readable (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
2     descriptor.writable = false;
3 };
4  
5 class User {
6  
7     name: string;
8     constructor(name: string){
9         this.name = name;
10     }
11  
12     @readable
13     print():void{
14         console.log(this.name);
15     }
16 }
17 let tom = new User("Tom");
18 tom.print = function(){console.log("print has been changed");}
19 tom.print();  // Tom

Декоратор readable с помощью выражения descriptor.writable = false;
устанавливает, что метод, к которому применяется данный декоратор, не может быть
изменен.

В итоге после применения данного декоратора следующая инструкция

1 tom.print = function(){console.log("print has been changed");}

не имеет смысла и не будет работать. Однако если бы декоратор не применялся, то
инструкция сработала бы.

Параметры декоратора
Декоратор может принимать параметры, которые позволяют настроить из вне поведение
декоратора. Например, немного изменим предыдущий пример:

1 function readable(onlyRead : boolean){
2  
3     return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) 
4         descriptor.writable = !onlyRead;
5     };
6 }
7  
8 class User {
9  
10     name: string;
11     constructor(name: string){
12         this.name = name;
13     }
14  
15     @readable(false)
16     print():void{
17         console.log(this.name);
18     }
19 }
20 let tom = new User("Tom");
21 tom.print = function(){console.log("print has been changed");}
22 tom.print();  // Tom

Теперь параметр readable принимает параметр типа boolean:

1 function readable(onlyRead : boolean){

Значение этого параметра используется для установки свойства descriptor.writable:

1 descriptor.writable = !onlyRead;

При этом сам декоратор должен возратить функцию, которая соответствует выше
рассмотренному определению функции декоратора.

При применении декоратора в скобках ему можно передать значения для параметров:

1 @readable(false)
2 print():void{

Параметры и выходной результат метода

Декоратор метода позволяет нам манипулировать параметрами и возвращаемым
результатом метода. Например, определим следующий декоратор:

1 function log(target: Object, method: string, descriptor: PropertyDescriptor){
2     let originalMethod = descriptor.value;
3     descriptor.value = function(...args: number[]){
4         console.log(JSON.stringify(args));
5         let returnValue = originalMethod.apply(this, args);
6         console.log(`${JSON.stringify(args)} => ${returnValue}`)
7         return returnValue;
8     }
9 }
10  
11 class Calculator{
12  
13     @log
14     add(x: number, y: number): number{
15         return x + y;
16     }
17 }
18  
19 let calc = new Calculator();
20 let z = calc.add(4, 5);
21 z = calc.add(6, 7);

Декоратор log логгирует (выводит на консоль) значения параметров и возвращаемый
результат метода. Свойство descriptor.value позволяет получить начальное значение
метода ­ то есть ту функцию, которую представляет метод.

1 let originalMethod = descriptor.value;

Затем происходит переустановка значения descriptor.value.

1 descriptor.value = function(...args: number[]){
2 }

Параметр ...args ­ это все те параметры, которые будут передаваться в функцию. И мы
можем логгировать эти параметры. Далее вызывается оригинальная функция, которой
передаются параметры args:

1 let returnValue = originalMethod.apply(this, args);

И таким образом мы можем получить результат вызова оригинальной функции и
возвратить его из функции. То есть фактически получается, что мы берем оригинальную
функцию, обертываем ее в какую­ту другую функцию, в которой опять же вызываем
оригинальную функцию и возвращаем ее результат.
Таким образом, новое значение descriptor.value принимает те же параметры, возвращает
тот же результат, что и оригинальная функция, но при этом добавляет некоторое
дополнительное поведение.

Далее мы можем применить декоратор, например, к методу add класса Calculator и вызвать
этот метод.

Декораторы параметров методов

Декоратор параметра метода представляет функцию, которая принимает три параметра:

1 function MyParameterDecorator(target: Object, propertyKey: string, parameterIndex: number
2     // код декоратора
3 }

Где первый параметр представляет конструктор класса, если метод статический, либо
прототип класса, если метод нестатический. А второй параметр представляет имя метода.
И третий параметр представляет порядковый индекс параметра в списке параметров.

Определим декоратор для параметра метода:

1 function logParameter(target: any, key : string, index : number) {
2     var metadataKey = `__log_${key}_parameters`;
3      
4     if (Array.isArray(target[metadataKey])) {
5         target[metadataKey].push(index);
6       }
6       }
7       else {
8         target[metadataKey] = [index];
9     }
10 }
11 function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
12  
13     var originalMethod = descriptor.value;
14     descriptor.value = function (...args: any[]) {
15  
16         var metadataKey = `__log_${key}_parameters`;
17         var indices = target[metadataKey];
18  
19         if (Array.isArray(indices)) { 
20             for (var i = 0; i < args.length; i++) { 
21          
22                 if (indices.indexOf(i) !== ‐1) { 
23                     var arg = args[i];
24                     var argStr = JSON.stringify(arg) || arg.toString();
25                     console.log(`${key} arg[${i}]: ${argStr}`);
26                 }
27             }
28             var result = originalMethod.apply(this, args);
29             return result;
30         }
31         else {
32             var a = args.map(a => (JSON.stringify(a) || a.toString())).join();
33             var result = originalMethod.apply(this, args);
34             var r = JSON.stringify(result);
35             console.log(`Call: ${key}(${a}) => ${r}`);
36             return result;
37         }
38     }
39     return descriptor;
40 }
41  
42 class User {
43  
44     private name: string;
45     constructor(name: string){
46         this.name = name;
47     }
48    @logMethod
49     setName(@logParameter name: string){
50         this.name = name;
51     }
52     print():void{
53         console.log(this.name);
54     }
54     }
55 }
56 let tom = new User("Tom");
57 tom.setName("Bob");
58 tom.setName("Sam");

Декоратор logParameter добавляет в прототип класса новое свойство metadataKey. Это
свойство представляет массив, который содержит индексы декорированных параметров.

Для чтения метаданных из свойства metadataKey применяется декоратор метода
logMethod, который перебирает все параметры метода, находит значения параметров по
индексам, которые определены декоратором параметра, и выводит на консоль названия и
значения декорированных параметров.
METANIT.COM
Сайт о программировании

                    

Декораторы свойств и методов доступа
Последнее обновление: 09.01.2023

            

Yandex Games

Bubble Hit

Декораторы свойств

Декоратор свойства представляет функцию, которая принимает два параметра:

1 function MyPropertyDecorator(target: Object, propertyKey: string){
2     // код декоратора
3 }

Где первый параметр представляет конструктор класса, если свойство статическое, либо
прототип класса, если свойство нестатическое. А второй параметр представляет имя
свойства.

Определим простейший декоратор для свойства:

1 function format() {
2   return function(target: Object, propertyKey: string) { 
3     let value : string;
4     const getter = function() {
5       return "Mr./Ms." + value;     // изменяем возвращаемое значение
6     };
7     const setter = function(newVal: string) {
8        if(newVal.length > 2) {   // добавляем проверку на длину строки
8        if(newVal.length > 2) {   // добавляем проверку на длину строки
9           value = newVal
10       }     
11     }; 
12     // устанавливает геттер и сеттер для свойства
13     Object.defineProperty(target, propertyKey, {    
14       get: getter,
15       set: setter
16     });
17   }
18 }
19  
20 class User {
21   
22     @format()
23     name: string;
24     constructor(name: string){
25         this.name = name;
26     }
27     print():void{
28         console.log(this.name);
29     }
30 }
31 let tom = new User("Tom");
32 tom.print();
33 tom.name = "Tommy";
34 tom.print();
35 tom.name = "To";
36 tom.print();

Декоратор format выполняет небольшое форматирование значение свойства. Для этого
вначале мы получаем значение свойства. Создаем геттер, который возвращает
отформатированное значение. Далее определяется сеттер, который устанавливает новое
значение для свойства. В нем мы можем проинспектировать, как устанавливается
свойство. Так, в данном случае не устанавливаем новое значение, если его длина меньше
2. И в конце удаляется старое свойство и создается новое с геттером и сеттером.

Вывод консоли браузера:

Mr./Ms.Tom 
Mr./Ms.Tommy 
Mr./Ms.Tommy 

Декоратор метода доступа

Декоратор метода доступа принимает три параметра:
1 function decorator(target: Object, propertyName: string, descriptor: PropertyDescriptor){ 
2     // код декоратора
3 }

Первый параметр представляет конструктора класса для статического метода, либо
прототип класса для обычного метода.

Второй параметр представляет название метода.

Третий параметр представляет объект PropertyDescriptor.

Определим простейший декоратор метода доступа:

1 function validator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
2     const oldSet = descriptor.set;
3   
4     descriptor.set = function(value: string) {
5         if (value === "admin") {
6             throw new Error("Invalid value");
7         }
8         if(oldSet!==undefined) oldSet.call(this, value);
9     }
10 }
11 class User {
12   
13     private _name: string;
14     constructor(name: string){
15         this.name = name;
16     }
17       
18     public get name(): string {
19         return this._name;
20     }
21     @validator
22     public set name(n: string) {
23         this._name = n;
24     }
25 }
26 let tom = new User("Tom");
27 console.log(tom.name);
28 tom.name= "admin";
29 console.log(tom.name);

Декоратор validator переопределяет поведение сеттера с помощью свойства
descriptor.set. Если передаваемое сеттеру значение представляет строку "admin", то
генерируется ошибка.
Декоратор достаточно применить только к геттеру или к сеттеру, в любом случае он будет
сразу применяться к обоим аксессорам.

И, к примеру, при вызове инструкции tom.name= "admin"; мы столкнемся с ошибкой:

Назад Содержание Вперед

            
METANIT.COM
Сайт о программировании

                    

Фабрики декораторов
Последнее обновление: 20.05.2021

            

Yandex Games
Free online
baloon
games

Декоратор класса, свойства, метода представляет обычную функцию, которая принимает
заданное количество параметров. Но что если мы хотим передавать в декоратор какие­то
дополнительные данные, которые могут быть известны только при применении
декоратора? В этом случае мы можем сконструировать фабрику декораторов (factory
decorator). Фабрика декоратора представляет функцию, которая в свою очередь
возвращает функцию декоратора.

Например, определим протейшую функцию декоратора:

1 function regex(pattern: string){
2     let expression = new RegExp(pattern);
3     return function regex(target: Object, propertyName: string){
4         let propertyValue = this[propertyName];
5  
6         // геттер
7         var getter = function () {
8             return propertyValue;
9         };
10   
11         // сеттер
12         var setter = function (newVal: string) {
13             let isValid: boolean = expression.test(newVal); 
14             if(isValid === false){
15                 throw new Error(`Value ${newVal} does not match ${pattern}`);
16             }
17             else{
18                 console.log(`${newVal} is valid`);
19             }
20         };
21         // удаляем свойство
22         if (delete this[propertyName]) {
23      
24             // И создаем новое свойство с геттером и сеттером
25             Object.defineProperty(target, propertyName, {
26                 get: getter,
27                 set: setter
28             });
29         }
30     }
31 }
32 class Account{
33  
34     @regex("^[a‐zA‐Z0‐9_.+‐]+@[a‐zA‐Z0‐9‐]+\.[a‐zA‐Z0‐9‐.]+$")
35     email: string;
36  
37     @regex("^[\+]?[(]?[0‐9]{3}[)]?[‐\s\.]?[0‐9]{3}[‐\s\.]?[0‐9]{4,6}$")
38     phone: string;
39  
40     constructor(email: string, phone: string){
41         this.email = email; this.phone = phone;
42     }
43 }
44 let acc = new Account("bir@gmail.com", "+23451235678");
45 acc.email = "bir_iki_yedi";

Декоратор regex принимает в качестве параметра регулярное выражение и при этом
возвращает функцию декоратора свойства. В декораторе в сеттере при установке для
свойства нового значения проверяется соответствие нового значения регулярному
выражению. Если нет соответствия, то генерируется ошибка.

Далее в классе Account к его свойствам email и phone применяется декоратор regex, но в
каждом случае в декоратор передается свое регулярное выражение, с помощью которого
можно произвести проверку:
Назад Содержание

            

Вам также может понравиться