← Назад к вопросам

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

2.2 Middle🔥 121 комментариев
#ООП#Фреймворки и библиотеки

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Полиморфизм в NestJS и его реализация

Это очень хороший вопрос, показывающий глубокое понимание архитектуры NestJS. Расскажу подробно о механизмах, позволяющих использовать полиморфизм в NestJS.

Основа: Dependency Injection контейнер

NestJS построен на мощной системе Dependency Injection (DI) контейнере, которая позволяет реализовывать полиморфизм через интерфейсы и абстрактные классы.

Механизмы полиморфизма в NestJS

1. Dependency Injection через интерфейсы

@Controller('users')
export class UserController {
  constructor(@Inject('IUserService') private userService: IUserService) {}
  
  @Get(':id')
  async findUser(@Param('id') id: string) {
    return this.userService.findById(id);
  }
}

@Module({
  providers: [
    {
      provide: 'IUserService',
      useClass: process.env.DB === 'postgres' ? UserService : MongoUserService,
    },
  ],
  controllers: [UserController],
})
export class UserModule {}

2. Абстрактные классы (более типобезопасно)

export abstract class BaseUserService {
  abstract findById(id: string): Promise<User>;
  abstract create(data: CreateUserDto): Promise<User>;
}

@Injectable()
export class PostgresUserService extends BaseUserService {
  constructor(private db: Database) {}
  
  async findById(id: string): Promise<User> {
    return this.db.query('SELECT * FROM users WHERE id = $1', [id]);
  }
}

@Injectable()
export class MongoUserService extends BaseUserService {
  constructor(private db: MongoClient) {}
  
  async findById(id: string): Promise<User> {
    return this.db.collection('users').findOne({ _id: id });
  }
}

@Module({
  providers: [
    {
      provide: BaseUserService,
      useClass: PostgresUserService,
    },
  ],
})
export class UserModule {}

3. Factory pattern для динамической инъекции

@Injectable()
export class UserServiceFactory {
  create(dbType: 'postgres' | 'mongo'): BaseUserService {
    if (dbType === 'postgres') {
      return new PostgresUserService(this.pgDb);
    } else {
      return new MongoUserService(this.mongoDb);
    }
  }
  
  constructor(
    private pgDb: PostgresDatabase,
    private mongoDb: MongoClient,
  ) {}
}

@Module({
  providers: [
    UserServiceFactory,
    {
      provide: BaseUserService,
      useFactory: (factory: UserServiceFactory) => 
        factory.create(process.env.DATABASE),
      inject: [UserServiceFactory],
    },
  ],
})
export class UserModule {}

4. Strategy pattern с полиморфизмом

interface PaymentStrategy {
  pay(amount: number, orderId: string): Promise<PaymentResult>;
}

@Injectable()
export class StripePaymentStrategy implements PaymentStrategy {
  async pay(amount: number, orderId: string): Promise<PaymentResult> {
    // Интеграция с Stripe
  }
}

@Injectable()
export class PayPalPaymentStrategy implements PaymentStrategy {
  async pay(amount: number, orderId: string): Promise<PaymentResult> {
    // Интеграция с PayPal
  }
}

@Injectable()
export class PaymentService {
  constructor(
    @Inject('PAYMENT_STRATEGY') private strategy: PaymentStrategy,
  ) {}
  
  async processPayment(amount: number, orderId: string) {
    return this.strategy.pay(amount, orderId);
  }
}

@Module({
  providers: [
    {
      provide: 'PAYMENT_STRATEGY',
      useClass: StripePaymentStrategy,
    },
    PaymentService,
  ],
})
export class PaymentModule {}

5. Использование Token провайдеров (рекомендуемый подход)

export const USER_SERVICE_TOKEN = Symbol('USER_SERVICE');

@Controller('users')
export class UserController {
  constructor(@Inject(USER_SERVICE_TOKEN) private userService: IUserService) {}
}

@Module({
  providers: [
    {
      provide: USER_SERVICE_TOKEN,
      useClass: process.env.DB === 'postgres' ? UserService : MongoUserService,
    },
  ],
})
export class UserModule {}

6. Conditional providers для окружения

@Module({
  providers: [
    {
      provide: 'DATABASE_SERVICE',
      useClass: process.env.NODE_ENV === 'production' 
        ? PostgresDatabaseService 
        : InMemoryDatabaseService,
    },
  ],
})
export class DatabaseModule {}

Практический пример: Полная архитектура

interface IEmailProvider {
  send(to: string, subject: string, body: string): Promise<void>;
}

@Injectable()
export class SendGridEmailProvider implements IEmailProvider {
  async send(to: string, subject: string, body: string) {
    // SendGrid реализация
  }
}

@Injectable()
export class MailgunEmailProvider implements IEmailProvider {
  async send(to: string, subject: string, body: string) {
    // Mailgun реализация
  }
}

@Injectable()
export class NotificationService {
  constructor(private emailProvider: IEmailProvider) {}
  
  async notifyUser(userId: string, message: string) {
    const user = await this.userService.findById(userId);
    await this.emailProvider.send(user.email, 'Notification', message);
  }
}

@Module({
  providers: [
    {
      provide: 'EMAIL_PROVIDER',
      useClass: process.env.EMAIL_PROVIDER === 'sendgrid' 
        ? SendGridEmailProvider 
        : MailgunEmailProvider,
    },
    NotificationService,
  ],
})
export class NotificationModule {}

Ключевые преимущества полиморфизма в NestJS

  • Flexibility — легко менять реализации без изменения потребителей
  • Testability — можно создавать mock реализации для тестов
  • Maintainability — чистое разделение ответственности
  • Scalability — легко добавлять новые реализации
  • Type Safety — полная поддержка TypeScript типов

Выводы

Полиморфизм в NestJS возможен благодаря мощной системе Dependency Injection контейнера, который позволяет инъектировать разные реализации интерфейсов и абстрактных классов. Это основа архитектуры NestJS и позволяет писать гибкий, тестируемый и поддерживаемый код. Ключевые механизмы: интерфейсы, абстрактные классы, провайдеры с useClass, factory функции и Symbol токены.

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