Название фичи: Mixins

Описание:
TypeScript, как и многие объектно-ориентированные языки, не позволяет использовать напрямую множественное наследование. Из-за этого, не смотря на то, что можно реализовать множество интерфейсов в классе, унаследовать его можно только от одного класса.
Примесь – объект, содержащий методы и свойства для реализации конкретного функционала.
Функциональность миксинов (mixins) частично позволяет унаследовать свойства и методы сразу двух и более классов, тем самым избавившись от дублирования в коде.

Аналог в Scala: mixins, traits

Решаемая проблема:
В какой-то момент в дочерних классах появляется необходимость в одинаковом функционале из-за чего возникает копипаст и нехватка множественного наследования:

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

Ограничения:

  • Миксин может унаследовать только те свойства и методы, которые непосредственно определены в применяемом классе, то есть он не будет работать, если применяемый класс наследует какие-то свойства / методы от другого класса.

  • Если родительские классы определяют метод с одним и тем же именем, то миксин наследует только тот метод, который копируется в него последним в функции applyMixins.

Плюсы:

  • Простота реализации;
  • Легкость переопределения содержащегося в миксине кода;
  • Гибкость подключения, возможность создания зависимых миксинов;
  • Использование паттерна не усложняет его понимание и поддержку, так как используется существующий механизм наследования;
  • Скорость вмешивания — чтобы замешать миксин подобным образом не требуется ни единого цикла;
  • Оптимальное использование памяти (отсутствие копирования)

Как решить проблему:

Создание временного класса на основе миксина и подстановка его в очередь наследования:

Синтаксис с примером:

// одноразовый миксин
class Disposable {
    isDisposed: boolean;
    dispose() {
        this.isDisposed = true;
    }

}

// активируемый миксин
class Activatable {
    isActive: boolean;
    activate() {
        this.isActive = true;
    }
    deactivate() {
        this.isActive = false;
    }
}

// новый класс, объединяющий обе примеси. Рассматриваем классы как интерфейсы и используем только типы, а не реализации
class SmartObject implements Disposable, Activatable {
    constructor() {}

    interact() {
        this.activate();
    }

    // создаем свойства-дублёры
    // Disposable
    isDisposed: boolean = false;
    dispose: () => void;

    // Activatable
    isActive: boolean = false;
    activate: () => void;
    deactivate: () => void;
}

//соединяем примеси в классе, создавая полную реализацию
applyMixins(SmartObject, [Disposable, Activatable]);

//функция для создания примесей. Пробегает по свойствам и копировать их в целевой элемент, 
//заполняя свойства-дублёры их реализациями.
function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        });
    });
}

Перекомпилированный в JSкод:

var Disposable = /** @class */ (function () {
    function Disposable() {
    }
    Disposable.prototype.dispose = function () {
        this.isDisposed = true;
    };
    return Disposable;
}());
var Activatable = /** @class */ (function () {
    function Activatable() {
    }
    Activatable.prototype.activate = function () {
        this.isActive = true;
    };
    Activatable.prototype.deactivate = function () {
        this.isActive = false;
    };
    return Activatable;
}());
var SmartObject = /** @class */ (function () {
    function SmartObject() {
        this.isDisposed = false;
        this.isActive = false;
    }
    SmartObject.prototype.interact = function () {
        this.activate();
    };
    return SmartObject;
}());
applyMixins(SmartObject, [Disposable, Activatable]);
function applyMixins(derivedCtor, baseCtors) {
    baseCtors.forEach(function (baseCtor) {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(function (name) {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        });
    });
}

Больше деталей: тут

results matching ""

    No results matching ""