Typescript: как расширить два класса?

я хочу сэкономить свое время и повторно использовать общий код между классами, который расширяет классы PIXI (2D библиотека визуализации webGl).

Объект Интерфейсы:

module Game.Core {
    export interface IObject {}

    export interface IManagedObject extends IObject{
        getKeyInManager(key: string): string;
        setKeyInManager(key: string): IObject;
    }
}

моя проблема в том, что код внутри getKeyInManager и setKeyInManager не изменится, и я хочу его повторно использовать, а не дублировать, вот реализация:

export class ObjectThatShouldAlsoBeExtended{
    private _keyInManager: string;

    public getKeyInManager(key: string): string{
        return this._keyInManager;
    }

    public setKeyInManager(key: string): DisplayObject{
        this._keyInManager = key;
        return this;
    }
}

то, что я хочу сделать, это автоматически добавить через Manager.add(), ключ, используемый в менеджере для ссылки объект внутри сам объект в его собственность _keyInManager.

Итак, давайте возьмем пример с текстурой. Вот идет TextureManager

module Game.Managers {
    export class TextureManager extends Game.Managers.Manager {

        public createFromLocalImage(name: string, relativePath: string): Game.Core.Texture{
            return this.add(name, Game.Core.Texture.fromImage("/" + relativePath)).get(name);
        }
    }
}

когда я делаю this.add(), Я хочу Game.Managers.Manager add() метод для вызова метода, который будет существовать на объект, возвращенный Game.Core.Texture.fromImage("/" + relativePath). Этот объект, в данном случае, будет Texture:

module Game.Core {
    // I must extends PIXI.Texture, but I need to inject the methods in IManagedObject.
    export class Texture extends PIXI.Texture {

    }
}

я знаю, что IManagedObject является интерфейсом и не может содержать реализацию, но я не знаю, что делать напишите, чтобы ввести класс ObjectThatShouldAlsoBeExtended внутри Texture класса. Зная, что тот же самый процесс потребуется для Sprite, TilingSprite, Layer и многое другое.

мне нужна опытная обратная связь/советы TypeScript здесь, это должно быть возможно сделать, но не несколькими расширениями, так как в то время возможен только один, я не нашел другого решения.

3 ответов


в TypeScript есть малоизвестная функция, которая позволяет использовать Mixins для создания повторно используемых небольших объектов. Вы можете составить их в более крупные объекты, используя множественное наследование (множественное наследование не разрешено для классов, но разрешено для mixins - которые похожи на интерфейсы с связанной имплененцией).

дополнительная информация о TypeScript Mixins

Я думаю, вы могли бы использовать этот метод для общих компонентов между многими классы в игре и повторно использовать многие из этих компонентов из одного класса в игре:

вот быстрая демонстрация Mixins... во-первых, ароматы, которые вы хотите смешать:

class CanEat {
    public eat() {
        alert('Munch Munch.');
    }
}

class CanSleep {
    sleep() {
        alert('Zzzzzzz.');
    }
}

затем волшебный метод для создания Mixin (вам нужно только один раз где-то в вашей программе...)

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    }); 
}

и затем вы можете создавать классы с множественным наследованием от mixin flavours:

class Being implements CanEat, CanSleep {
        eat: () => void;
        sleep: () => void;
}
applyMixins (Being, [CanEat, CanSleep]);

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

var being = new Being();

// Zzzzzzz...
being.sleep();

Я бы предложил использовать новый подход mixins, описанный там: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/

этот подход лучше, чем подход "applyMixins", описанный Fenton, потому что автокомпилятор поможет вам и покажет все методы / свойства как из базового, так и из 2-го классов наследования.

этот подход может быть проверен на игровая площадка TS сайт.

вот реализация:

class MainClass {
    testMainClass() {
        alert("testMainClass");
    }
}

const addSecondInheritance = (BaseClass: { new(...args) }) => {
    return class extends BaseClass {
        testSecondInheritance() {
            alert("testSecondInheritance");
        }
    }
}

// Prepare the new class, which "inherits" 2 classes (MainClass and the cass declared in the addSecondInheritance method)
const SecondInheritanceClass = addSecondInheritance(MainClass);
// Create object from the new prepared class
const secondInheritanceObj = new SecondInheritanceClass();
secondInheritanceObj.testMainClass();
secondInheritanceObj.testSecondInheritance();

к сожалению, typescript не поддерживает множественное наследование. Поэтому нет абсолютно тривиального ответа, вам, вероятно, придется реструктурировать свою программу

вот несколько советов:

  • Если этот дополнительный класс содержит поведение, которое разделяют многие подклассы, имеет смысл вставить его в иерархию классов где-то вверху. Возможно, вы могли бы получить общий суперкласс Sprite, Texture, Layer,... из этого класс ? Это будет хорошим выбором, если вы можете найти хорошее место в иерархия типа. Но я бы не рекомендовал просто вставлять этот класс в случайную точку. Наследование выражает "является-отношением", например, собака является животным, текстура является экземпляром этого класса. Вы должны спросить себя, действительно ли это моделирует отношения между объектами в вашем коде. Логическое дерево наследования очень ценно

  • Если дополнительный класс не подходит логически в иерархии типов, можно использовать агрегацию. Это означает, что вы добавляете переменную экземпляра типа этого класса в общий суперкласс Sprite, Texture, Layer, ... Затем вы можете получить доступ к переменной с ее геттером / сеттером во всех подклассах. Это модель "имеет - отношение".

  • вы также можете преобразовать свой класс в интерфейс. Затем вы можете расширить интерфейс со всеми своими классами, но было бы правильно реализовать методы в каждом классе. Это означает некоторую избыточность кода, но в этом случае не так много.

вы должны решить для себя, какой подход вам больше нравится. Лично я бы рекомендовал преобразовать класс в интерфейс.

один совет: Typescript предлагает свойства, которые являются синтаксическим сахаром для геттеров и сеттеров. Возможно, вы захотите взглянуть на это: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/