Развернутые декораторы машинописного текста: руководство для начинающих по программированию с наддувом
Предпосылки
- Основы TypeScript
- Основные понятия объектно-ориентированного программирования (ООП)
- Знание JavaScript
- ES6 и далее
Введение
Декораторы — это функция, представленная в ES6 и поддерживаемая в TypeScript. Декораторы позволяют нам присоединять метаданные и изменять поведение классов, методов, средств доступа, свойств или методов во время выполнения.
Декораторы обозначаются символом @
, за которым следует имя декоратора, и могут быть размещены непосредственно перед объявлением, которое они украшают. Это не что иное, как функции, которые применяются к цели, и они получают информацию о цели в качестве параметров.
Давайте посмотрим на синтаксис декораторов на примере:
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${target.constructor.name}.${propertyKey} with arguments: ${JSON.stringify(args)}`); const result = originalMethod.apply(this, args); console.log(`Result: ${result}`); return result; }; return descriptor; } class Calculator { @log add(a: number, b: number): number { return a + b; } } const calculator = new Calculator(); console.log(calculator.add(2, 3)); // Output:- // Calling Calculator.add with arguments: [2,3] // Result: 5 // 5
В приведенном выше примере у нас есть декоратор с именем log
, который регистрирует вызовы методов и их аргументы до и после выполнения исходного метода. Декоратор log
получил 3 параметра: target
(в данном случае прототип класса), property key
(имя метода), descriptor
. Мы более подробно рассмотрим эти параметры в следующих разделах.
log
декоратор изменил значение дескриптора, чтобы обернуть исходный метод, и добавил функциональность ведения журнала.
Когда вызывается метод add
класса Calculator
, декоратор log
перехватывает вызов, регистрирует аргументы, вызывает исходный метод, регистрирует результат и возвращает его. Это позволяет нам изменять или дополнять поведение декорированного метода.
Параметры декоратора
В предыдущем разделе мы узнали о декораторах и их синтаксисе. Теперь давайте подробнее рассмотрим различные параметры, к которым можно получить доступ в функции декоратора:
- target:- Параметр
target
относится к классу или прототипу декорируемого объекта. Он представляет функцию-конструктор класса, когда декоратор применяется к объявлению класса. В случае методов экземпляра или статических методов это относится к прототипу класса. Параметрtarget
позволяет использовать для доступа и изменения класса или его свойств во время выполнения. - propertyKey:- Параметр
propertyKey
представляет имя декорированного метода или свойства. Для методов instance это соответствует имени декорируемого метода. Для статических методов это также включает имя класса. В случае свойств это относится к имени украшаемого свойства. ПараметрpropertyKey
позволяет нам идентифицировать конкретный декорируемый метод или свойство и выполнять действия на его основе. - descriptor:- Параметр
descriptor
— это объект, который содержит дескриптор свойства декорированного метода или свойства. Он включает настраиваемые свойства, такие как методыvalue
,writable
,enumerable
иget/set
, если применимо.descriptor
позволяет нам изменить или заменить исходный метод или свойство новой реализацией. Манипулируяdescriptor
, мы можем изменить поведение декорированного объекта.
В функции декоратора мы можем использовать эти параметры для проверки и изменения поведения декорированного объекта.
Стоит отметить, что точная структура параметра descriptor
может различаться в зависимости от типа декорируемого объекта (метод, свойство, метод доступа) и от того, является ли он членом-экземпляром или статическим членом.
Тип декораторов
- Декораторы классов. Эти декораторы применяются к классам и позволяют нам изменять или расширять поведение самого класса. В качестве цели они получают конструктор класса. Мы можем использовать их для добавления дополнительных свойств или методов к классу, применения примесей или изменения метаданных класса. Вот пример:
function classDecorator(constructor: Function) { // Add a new property to the class constructor.prototype.newProperty = 'Hello, world!'; } @classDecorator class MyClass { // ... } const instance = new MyClass(); console.log(instance.newProperty); // Output: Hello, world
- Декораторы методов. Эти декораторы используются для изменения или расширения поведения метода внутри класса. Они применяются к самому методу и получают три параметра: целевой класс (или прототип, если метод статический), ключ свойства и дескриптор свойства. Мы можем использовать их для выполнения действий до или после вызова метода, изменения параметров метода или возвращаемого значения или добавления дополнительной логики. Вот пример:
function methodDecorator(target: any, methodName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${methodName} with arguments: ${args}`); const result = originalMethod.apply(this, args); console.log(`Method ${methodName} returned: ${result}`); return result; }; } class MyClass { @methodDecorator greet(name: string) { return `Hello, ${name}!`; } } const instance = new MyClass(); instance.greet('John'); // Output: Calling greet with arguments: John // Method greet returned: Hello, John!
- Декораторы параметров. Эти декораторы используются для изменения или добавления метаданных к параметру в методе или конструкторе. Они получают три параметра: целевой класс (или прототип, если параметр принадлежит методу), имя метода (или конструктора) и индекс параметра. Мы можем использовать их для выполнения действий или проверки значения параметра или добавления дополнительных метаданных. Вот пример:
function parameterDecorator(target: any, methodName: string, parameterIndex: number) { const args = target[methodName].toString().match(/\(([^)]*)\)/)[1].split(', '); console.log(`Parameter ${parameterIndex} of ${methodName} in ${target.constructor.name} is ${args[parameterIndex]}`); } class MyClass { greet(@parameterDecorator name: string) { console.log(`Hello, ${name}!`); } } const instance = new MyClass(); instance.greet('John'); // Output: Parameter 0 of greet in MyClass is name // Hello, John!
- Декораторы свойств. Эти декораторы используются для изменения или добавления метаданных к свойству внутри класса. Он получает два параметра: целевой класс (или прототип, если свойство статическое) и имя свойства. Мы можем использовать их, чтобы добавить к свойству дополнительное поведение или метаданные.
- Декораторы доступа. Декораторы доступа можно использовать для изменения или добавления метаданных в геттер или сеттер внутри класса. Декоратор доступа применяется к методу получения или установки и получает три параметра: целевой класс (или прототип, если метод доступа принадлежит статическому свойству), имя свойства и дескриптор свойства.
function accessorDecorator(target: any, propertyName: string, descriptor: PropertyDescriptor) { const originalGetter = descriptor.get; descriptor.get = function () { console.log(`Getting ${propertyName}`); return originalGetter.call(this); }; const originalSetter = descriptor.set; descriptor.set = function (value: any) { console.log(`Setting ${propertyName} to: ${value}`); originalSetter.call(this, value); }; } class MyClass { private _myProperty: string; @accessorDecorator get myProperty(): string { return this._myProperty; } set myProperty(value: string) { this._myProperty = value; } } const instance = new MyClass(); instance.myProperty = 'Hello, world!'; // Output: Setting myProperty to: Hello, world! console.log(instance.myProperty); // Output: Getting myProperty
В приведенном выше примере accessorDecorator
применяется к методам получения и установки свойства myProperty
. Декоратор изменяет поведение, добавляя журналы консоли перед получением и установкой значения свойства.
Фабрики декораторов
Фабрики декораторов — это функции, которые возвращают функции декораторов. Фабрики декораторов предоставляют способ создания параметризованных декораторов, которые могут принимать параметры конфигурации.
Чтобы лучше понять фабрики декораторов, давайте разберем концепцию на примере.
function validateRange(min: number, max: number) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { for (const arg of args) { if (arg < min || arg > max) { throw new Error(`Invalid argument ${arg} for "${propertyKey}".`); } } return originalMethod.apply(this, args); }; }; } class MathOperations { @validateRange(1, 10) multiply(a: number, b: number): number { return a * b; } } const math = new MathOperations(); console.log(math.multiply(5, 12)); // Throws an error: Invalid argument 12 for "multiply".
В этом примере у нас есть фабрика декораторов с именем validateRange
. Он принимает два параметра, min
и max
, и возвращает функцию-декоратор. Функция декоратора применяется к методу multiply
класса MathOperations
с использованием синтаксиса @validateRange(1, 10)
.
Функция декоратора проверяет, не выходит ли какой-либо из аргументов метода за пределы указанного диапазона (min
и max
). Если какой-либо аргумент недействителен, он выдает ошибку. В противном случае он вызывает исходный метод.
Используя фабрики декораторов, мы можем создавать декораторы, которые принимают различные параметры конфигурации, что делает их более гибкими и пригодными для повторного использования. Фабрики декораторов позволяют нам инкапсулировать создание декораторов с определенным поведением и легко применять их к классам, методам, свойствам или параметрам в TypeScript.
Порядок выполнения декораторов
Когда несколько декораторов применяются к одной и той же цели (классу, методу, свойству и т. д.), порядок их выполнения соответствует определенному шаблону. Рассмотрим порядок выполнения на примере:
function classDecorator1(target: any) { console.log("Executing class decorator 1"); } function classDecorator2(target: any) { console.log("Executing class decorator 2"); } function methodDecorator1(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("Executing method decorator 1"); } function methodDecorator2(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("Executing method decorator 2"); } function propertyDecorator1(target: any, propertyKey: string) { console.log("Executing property decorator 1"); } function propertyDecorator2(target: any, propertyKey: string) { console.log("Executing property decorator 2"); } function parameterDecorator1(target: any, propertyKey: string | symbol, parameterIndex: number) { console.log("Executing parameter decorator 1"); } function parameterDecorator2(target: any, propertyKey: string | symbol, parameterIndex: number) { console.log("Executing parameter decorator 2"); } function accessorDecorator1(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("Executing accessor decorator 1"); } function accessorDecorator2(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("Executing accessor decorator 2"); } @classDecorator1 @classDecorator2 class ExampleClass { @propertyDecorator1 @propertyDecorator2 exampleProperty: string; constructor( @parameterDecorator1 parameter1: string, @parameterDecorator2 parameter2: number ) {} @methodDecorator1 @methodDecorator2 exampleMethod() {} @accessorDecorator1 @accessorDecorator2 get exampleAccessor() { return this.exampleProperty; } @accessorDecorator1 @accessorDecorator2 set exampleAccessor(value: string) { this.exampleProperty = value; } } // Output // Executing property decorator 2 // Executing property decorator 1 // Executing method decorator 2 // Executing method decorator 1 // Executing accessor decorator 2 // Executing accessor decorator 1 // Executing parameter decorator 2 // Executing parameter decorator 1 // Executing class decorator 2 // Executing class decorator 1
Из приведенного выше примера мы можем наблюдать следующие два момента, касающиеся порядка выполнения декораторов:
- Декораторы того же типа, которые находятся ближе к цели, выполняются первыми. Например, в случае декораторов свойств
@propertyDecorator2
выполняется до@propertyDecorator1
. Это же правило относится ко всем типам декораторов. - Порядок выполнения различных типов декораторов следующий:
1. Декораторы свойств
2. Декораторы методов
3. Декораторы доступа
4. Декораторы параметров
5 , Декораторы классов
Следуя этим правилам, TypeScript обеспечивает согласованный и предсказуемый порядок выполнения декораторов для различных целей.
Приложения декораторов
- Ведение журнала.Декораторы можно использовать для добавления функции ведения журнала к функциям или методам. Например, мы можем создать декоратор
log
, который регистрирует входные аргументы и возвращаемое значение функции. - Аутентификация. Их можно использовать для принудительной проверки подлинности перед предоставлением доступа к определенным функциям или методам. Например, мы можем создать декоратор
authenticate
, который проверяет статус аутентификации пользователя перед выполнением метода. - Проверка: – можно использовать декораторы для проверки входных данных параметров функции. Например, мы можем создать декоратор
validate
, который проверяет, соответствуют ли аргументы определенным критериям. - Мемоизация. Декораторы могут использоваться для реализации мемоизации, которая представляет собой метод, который кэширует результаты дорогостоящих вызовов функций для повышения производительности. Например, мы можем создать декоратор
memoize
, который кэширует возвращаемое значение на основе аргументов функции. - Внедрение зависимостей. Декораторы можно использовать для обработки внедрения зависимостей, когда зависимости автоматически внедряются в класс или функцию. Например, мы можем создать декоратор
inject
, который автоматически внедряет зависимости на основе метаданных.
Заключение
В заключение можно сказать, что декораторы в TypeScript — это мощная функция, позволяющая изменять и расширять код во время выполнения. Они обеспечивают гибкость и возможность повторного использования, позволяя разработчикам добавлять функции и поведения к классам и их членам без прямого изменения реализации. Они имеют широкий спектр приложений, улучшая функциональность кода при сохранении чистой и модульной конструкции. Декораторы позволяют разработчикам создавать выразительные и удобные в сопровождении приложения TypeScript, эффективно настраивая и расширяя свою кодовую базу.