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