Что позволяет использовать полиморфизм в NestJS?

Ответ

NestJS, построенный на TypeScript, активно использует полиморфизм через свою систему внедрения зависимостей (DI). Это позволяет создавать гибкие и легко тестируемые приложения.

  1. Абстрактные классы и наследование. Можно определить общую логику в базовом классе и переопределять её в наследниках.

    export abstract class CacheService {
      abstract get(key: string): Promise<string>;
      abstract set(key: string, value: string): Promise<void>;
    }
    
    @Injectable()
    export class RedisCacheService extends CacheService {
      async get(key: string) { /* ...реализация для Redis */ }
      async set(key: string, value: string) { /* ... */ }
    }
    
    @Injectable()
    export class InMemoryCacheService extends CacheService {
      private cache = new Map();
      async get(key: string) { return this.cache.get(key); }
      // ...
    }

    Затем в провайдерах модуля можно указать, какую реализацию использовать:

    { provide: CacheService, useClass: RedisCacheService }
  2. Интерфейсы и их токены. Поскольку интерфейсы TypeScript не существуют во время выполнения, NestJS использует строковые или символьные токены для связи абстракции с реализацией.

    export const PAYMENT_SERVICE = Symbol('IPaymentService');
    export interface IPaymentService {
      charge(amount: number): Promise<Receipt>;
    }
    
    @Injectable()
    export class StripeService implements IPaymentService { /* ... */ }
    
    // В модуле
    providers: [
      { provide: PAYMENT_SERVICE, useClass: StripeService }
    ]
    
    // В конструкторе сервиса
    constructor(@Inject(PAYMENT_SERVICE) private paymentService: IPaymentService) {}
  3. Динамические провайдеры (useFactory, useExisting). Позволяют на лету решать, какую конкретную реализацию инжектировать, основываясь на конфигурации или контексте.

    {
      provide: 'CONNECTION',
      useFactory: (configService: ConfigService) => {
        if (configService.get('USE_PRODUCTION_DB')) {
          return new ProductionDatabaseConnection();
        }
        return new MockDatabaseConnection();
      },
      inject: [ConfigService]
    }

Таким образом, полиморфизм в NestJS — это краеугольный камень архитектуры, позволяющий легко подменять реализации, писать модульные тесты и соблюдать принцип инверсии зависимостей (DIP).

Ответ 18+ 🔞

Э, слушай, вот NestJS на TypeScript — это вообще ёперный театр с полиморфизмом через их систему внедрения зависимостей. Без неё там нихуя не работает, а с ней — гибкость пиздец и тесты писать одно удовольствие, честно.

  1. Абстрактные классы и наследование. Берёшь, делаешь какой-нибудь общий класс-заглушку, где объявляешь, что должно быть, а как — хуй его знает. А потом плодишь наследников, которые уже реализуют эту хрень по-своему.

    export abstract class CacheService {
      abstract get(key: string): Promise<string>;
      abstract set(key: string, value: string): Promise<void>;
    }
    
    @Injectable()
    export class RedisCacheService extends CacheService {
      async get(key: string) { /* ...реализация для Redis */ }
      async set(key: string, value: string) { /* ... */ }
    }
    
    @Injectable()
    export class InMemoryCacheService extends CacheService {
      private cache = new Map();
      async get(key: string) { return this.cache.get(key); }
      // ...
    }

    А потом в настройках модуля просто указываешь, какую конкретно реализацию подсовывать, когда кто-то просит CacheService. Гениально и просто, как тапок.

    { provide: CacheService, useClass: RedisCacheService }
  2. Интерфейсы и их токены. Вот тут, бля, начинается магия. Интерфейсы в TypeScript — они же на этапе выполнения испаряются, как последняя бутылка пива на троих. Поэтому NestJS использует токены — строки или символы, чтобы связать абстракцию с конкретной хуйнёй.

    export const PAYMENT_SERVICE = Symbol('IPaymentService');
    export interface IPaymentService {
      charge(amount: number): Promise<Receipt>;
    }
    
    @Injectable()
    export class StripeService implements IPaymentService { /* ... */ }
    
    // В модуле
    providers: [
      { provide: PAYMENT_SERVICE, useClass: StripeService }
    ]
    
    // В конструкторе сервиса
    constructor(@Inject(PAYMENT_SERVICE) private paymentService: IPaymentService) {}

    Смотри, какая хитрая жопа: ты в коде опираешься на интерфейс IPaymentService, а фреймворк под капотом по токену PAYMENT_SERVICE находит, какую именно реализацию тебе впендюрить. Доверия к системе — ебать ноль, но работает, сука, как часы.

  3. Динамические провайдеры (useFactory, useExisting). Это вообще песня. Тут ты уже на лету решаешь, какую конкретную реализацию подсунуть, в зависимости от конфига или того, что творится вокруг. Овердохуища гибкости.

    {
      provide: 'CONNECTION',
      useFactory: (configService: ConfigService) => {
        if (configService.get('USE_PRODUCTION_DB')) {
          return new ProductionDatabaseConnection();
        }
        return new MockDatabaseConnection();
      },
      inject: [ConfigService]
    }

    То есть в одном случае тебе продакшн-база, а в другом — заглушка для тестов. И всё это решается в одном месте, а не размазано по всему коду. Красота, ядрёна вошь.

Короче, полиморфизм в NestJS — это не просто какая-то академическая хуйня из учебника. Это реальный рабочий инструмент, на котором всё держится. Благодаря этому можно легко подменять одну хуйню на другую, тесты изоляцией писать и в целом не нарушать принципы нормальной архитектуры. Без этого был бы пиздец, а не фреймворк.