Подтвердить что ты не робот

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 обратная связь/советы здесь, это должно быть возможно сделать, но не с помощью нескольких расширений, поскольку в то время возможно только одно, я не нашел другого решения.

4b9b3361

Ответ 1

В TypeScript есть небольшая известная функция, которая позволяет вам использовать 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:

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

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

var being = new Being();

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

Ответ 2

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

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

Этот подход может быть проверен на сайте TS Playground.

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

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();

Ответ 3

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

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

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

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

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

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

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

Ответ 4

В JavaScript (ES7) появилась новая функция, называемая декораторами, и, используя эту функцию, плюс небольшую библиотеку, называемую typescript-mix, вы можете использовать mixins для множественного наследования всего за пару строк.

// The following line is only for intellisense to work
interface Shopperholic extends Buyer, Transportable {}

class Shopperholic {
  // The following line is where we "extend" from other 2 classes
  @use( Buyer, Transportable ) this 
  price = 2000;
}

Ответ 5

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

Сначала объявите интерфейсы, которые вы хотите реализовать в своем целевом классе:

interface IBar {
  doBarThings(): void;
}

interface IBazz {
  doBazzThings(): void;
}

class Foo implements IBar, IBazz {}

Теперь мы должны добавить реализацию к классу Foo. Мы можем использовать классовые миксины, которые также реализуют эти интерфейсы:

class Base {}

type Constructor<I = Base> = new (...args: any[]) => I;

function Bar<T extends Constructor>(constructor: T) {
  return class extends constructor implements IBar {
    public doBarThings() {
      console.log("Do bar!");
    }
  };
}

function Bazz<T extends Constructor>(constructor: T) {
  return class extends constructor implements IBazz {
    public doBazzThings() {
      console.log("Do bazz!");
    }
  };
}

Расширьте класс Foo классом mixins:

class Foo extends Bar(Bazz(Base)) implements IBar, IBazz {
  public doBarThings() {
    super.doBarThings();
    console.log("Override mixin");
  }
}

const foo = new Foo();
foo.doBazzThings(); // Do bazz!
foo.doBarThings(); // Do bar! // Override mixin