Разрешено ли множественное наследование в TypeScript?

«Разрешено ли множественное наследование в TypeScript?» — вопрос из категории TypeScript, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, TypeScript, как и JavaScript, не поддерживает множественное наследование классов в классическом понимании (когда класс наследует реализацию от нескольких родительских классов). Попытка написать class C extends A, B {} вызовет ошибку.

Однако, для повторного использования кода из нескольких источников в TypeScript используются альтернативные подходы:

  1. Миксины (Mixins): Это функции, которые принимают класс и возвращают новый класс с расширенной функциональностью.

    class CanLog {
        log(msg: string) { console.log(msg); }
    }
    
    class CanSerialize {
        serialize() { return JSON.stringify(this); }
    }
    
    type Constructor = new (...args: any[]) => {};
    
    function Loggable<TBase extends Constructor>(Base: TBase) {
        return class extends Base {
            log = CanLog.prototype.log;
        };
    }
    
    function Serializable<TBase extends Constructor>(Base: TBase) {
        return class extends Base {
            serialize = CanSerialize.prototype.serialize;
        };
    }
    
    class MyService {}
    const EnhancedService = Serializable(Loggable(MyService));
    const instance = new EnhancedService();
    instance.log('Hello'); // OK
    instance.serialize(); // OK
  2. Композиция: Часто является более предпочтительным и гибким подходом. Вместо наследования класс содержит экземпляры других классов (делегирует им работу).

    class Logger { log(msg: string) { /* ... */ } }
    class Serializer { serialize(data: any) { /* ... */ } }
    
    class MyService {
        private logger = new Logger();
        private serializer = new Serializer();
    
        process(data: any) {
            this.logger.log('Processing');
            const result = this.serializer.serialize(data);
            // ...
        }
    }
  3. Реализация нескольких интерфейсов: Класс может реализовывать несколько интерфейсов, что гарантирует наличие определенных методов, но не предоставляет их реализацию.

    interface Loggable { log(): void; }
    interface Serializable { serialize(): string; }
    
    class MyComponent implements Loggable, Serializable {
        log() { /* реализация */ }
        serialize() { /* реализация */ }
    }

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