Теория 📔
July 19

Паттерн "Декоратор"

Паттерн Декоратор (Decorator) позволяет динамически добавлять новое поведение объектам. В контексте Angular и TypeScript, декораторы могут быть использованы для обогащения классов, методов или свойств дополнительной функциональностью.

Начнем с создания простого декоратора, который будет добавлять логирование к методам:

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Method ${propertyKey} called with args: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyKey} returned: ${result}`);
    return result;
  };

  return descriptor;
}

Применим декоратор к методу класса:

class ExampleService {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const service = new ExampleService();
service.add(2, 3);

Когда метод add будет вызван, декоратор Log добавит логирование до и после выполнения метода.

Пример паттерна Декоратор на Angular

В Angular декораторы часто используются для создания сервисов, компонентов и директив. Мы можем создать декоратор для добавления функциональности к Angular-сервису.

Создадим декоратор, который будет логировать вызовы методов сервиса:

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Method ${propertyKey} called with args: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyKey} returned: ${result}`);
    return result;
  };

  return descriptor;
}

Применим декоратор

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  @Log
  fetchData(): string {
    // Некоторая логика получения данных
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
      sum += i;
    }
    return `Fetched data: ${sum}`;
  }
}

Используем сервис в компоненте

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-root',
  template: `<h1>{{ data }}</h1>`
})
export class AppComponent implements OnInit {
  data: string;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.data = this.dataService.fetchData();
  }
}

Когда метод fetchData будет вызван, декоратор Log добавит логирование до и после выполнения метода.

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

Создадим декоратор LogClass, который будет выводить в консоль сообщение о создании класса.

function LogClass(target: any) {
  const original = target;

  // Конструктор-прокси
  function construct(constructor, args) {
    const c: any = function () {
      return constructor.apply(this, args);
    };
    c.prototype = constructor.prototype;
    const instance = new c();
    console.log(`Instance of ${original.name} created:`, instance);
    return instance;
  }

  // Новый конструктор
  const f: any = function (...args) {
    console.log(`Creating instance of ${original.name}`);
    return construct(original, args);
  };

  f.prototype = original.prototype;

  return f;
}

Применим декоратор LogClass к сервису Angular:

import { Injectable } from '@angular/core';

@LogClass
@Injectable({
  providedIn: 'root'
})
export class MyService {
  constructor() {
    console.log('MyService instance created');
  }

  sayHello() {
    return 'Hello from MyService';
  }
}

Создадим компонент, который использует наш сервис:

import { Component, OnInit } from '@angular/core';
import { MyService } from './my.service';

@Component({
  selector: 'app-root',
  template: `<h1>{{ message }}</h1>`
})
export class AppComponent implements OnInit {
  message: string;

  constructor(private myService: MyService) {}

  ngOnInit() {
    this.message = this.myService.sayHello();
  }
}

Когда Angular создаст экземпляр MyService, в консоли будет выведено сообщение, что экземпляр класса был создан. Используя декораторы классов, мы можем добавлять поведение ко всем экземплярам класса. В этом примере мы создали декоратор, который выводит сообщение в консоль при создании экземпляра класса. Это может быть полезно для логирования, мониторинга или добавления другой функциональности, которую мы хотим применить ко всем экземплярам класса.

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