Ответ
Нет, TypeScript, как и JavaScript, не поддерживает множественное наследование классов в классическом понимании (когда класс наследует реализацию от нескольких родительских классов). Попытка написать class C extends A, B {} вызовет ошибку.
Однако, для повторного использования кода из нескольких источников в TypeScript используются альтернативные подходы:
-
Миксины (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 -
Композиция: Часто является более предпочтительным и гибким подходом. Вместо наследования класс содержит экземпляры других классов (делегирует им работу).
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); // ... } } -
Реализация нескольких интерфейсов: Класс может реализовывать несколько интерфейсов, что гарантирует наличие определенных методов, но не предоставляет их реализацию.
interface Loggable { log(): void; } interface Serializable { serialize(): string; } class MyComponent implements Loggable, Serializable { log() { /* реализация */ } serialize() { /* реализация */ } }
В своей практике я чаще всего использую композицию и интерфейсы, так как это приводит к более слабо связанному и тестируемому коду.
Ответ 18+ 🔞
Да ты посмотри, какая история интересная вырисовывается! TypeScript, как и его предок JavaScript, наотрез отказывается от классического множественного наследования, где можно тупо написать class C extends A, B {}. Ну, то есть попробовать-то можно, но получишь ошибку прямо в лицо, как будто наступил на грабли — ёпта, больно и обидно.
Но народ наш не лыком шит, и придумали обходные пути, чтобы не остаться с носом.
-
Миксины (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'); // Опа, работает! instance.serialize(); // И это тоже! Удивление пиздец. -
Композиция. А вот это, я тебе скажу, мой любимый способ. Забудь про "является", думай "имеет". Вместо того чтобы пытаться родиться от двух отцов (что, согласись, странно), твой класс просто содержит в себе нужные штуки и пользуется ими. Гораздо проще и надёжнее, доверия к этому подходу — овердохуища.
// Отдельные, независимые модули 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); // ... } }Чувак, это как собрать комп из готовых блоков, а не пытаться вырастить один супер-блок со всеми функциями сразу.
-
Несколько интерфейсов. А это про контракты. Ты можешь классу сказать: "Слушай, дружок, ты обязан уметь
log()иserialize(). Как именно — твои проблемы, но методы должны быть!" Чистая формальность, но иногда очень полезная.interface Loggable { log(): void; } interface Serializable { serialize(): string; } class MyComponent implements Loggable, Serializable { log() { /* реализуй сам, я не мамка твоя */ } serialize() { /* тут тоже */ } }
Если честно, в жизни я чаще всего пользуюсь композицией и интерфейсами. Код получается менее спагеттиобразным, его проще тестировать и менять. Миксины — это мощно, но выглядят они иногда как пиздопроебибна какая-то, и не каждый в команде сразу поймёт, что тут происходит. Так что выбирай по ситуации, но помни — прямое множественное наследование в TypeScript это хуй с горы, его нет.