Я хочу сэкономить свое время и повторно использовать общий код для разных классов, который расширяет классы 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;
}
}
Я хочу автоматически добавить через a 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, это должно быть возможно, но не с помощью нескольких расширений, поскольку в то время возможно только одно, другого решения я не нашел.
источник
Ответы:
В TypeScript есть малоизвестная функция, позволяющая использовать миксины для создания повторно используемых небольших объектов. Вы можете скомпоновать их в более крупные объекты, используя множественное наследование (множественное наследование не разрешено для классов, но разрешено для миксинов, которые похожи на интерфейсы со связанной реализацией).
Дополнительная информация о миксинах TypeScript
Я думаю, вы могли бы использовать эту технику, чтобы разделять общие компоненты между многими классами в вашей игре и повторно использовать многие из этих компонентов из одного класса в вашей игре:
Вот быстрая демонстрация Mixins ... во-первых, вкусы, которые вы хотите смешать:
class CanEat { public eat() { alert('Munch Munch.'); } } class CanSleep { sleep() { alert('Zzzzzzz.'); } }
Затем волшебный метод создания миксинов (вам понадобится только один раз где-нибудь в вашей программе ...)
function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { if (name !== 'constructor') { derivedCtor.prototype[name] = baseCtor.prototype[name]; } }); }); }
А затем вы можете создавать классы с множественным наследованием от разновидностей миксинов:
class Being implements CanEat, CanSleep { eat: () => void; sleep: () => void; } applyMixins (Being, [CanEat, CanSleep]);
Обратите внимание, что в этом классе нет реальной реализации - достаточно, чтобы он соответствовал требованиям «интерфейсов». Но когда мы используем этот класс - все работает.
var being = new Being(); // Zzzzzzz... being.sleep();
источник
Я бы предложил использовать описанный там новый подход миксинов: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/
Этот подход лучше, чем подход «applyMixins», описанный Фентоном, потому что автокомпилятор поможет вам и покажет все методы / свойства как из базового, так и из второго классов наследования.
Этот подход можно проверить на сайте 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();
источник
SecondInheritanceClass
не определено на цели или я что - то отсутствует? При загрузке этого кода на игровую площадку TS он говоритexpecting =>
. Наконец, не могли бы вы точно рассказать, что происходит вaddSecondInheritance
функции, например, для чегоnew (...args)
?secondInheritanceObj.some()
и не получаю предупреждающих сообщений.К сожалению, машинописный текст не поддерживает множественное наследование. Следовательно, полностью тривиального ответа нет, вам, вероятно, придется реструктурировать свою программу.
Вот несколько предложений:
Если этот дополнительный класс содержит поведение, которое разделяют многие из ваших подклассов, имеет смысл вставить его в иерархию классов где-нибудь наверху. Может быть, вы могли бы получить общий суперкласс Sprite, Texture, Layer, ... из этого класса? Это будет хороший выбор, если вы сможете найти хорошее место в типе hirarchy. Но я бы не рекомендовал просто вставлять этот класс в случайном месте. Наследование выражает «есть - отношение», например, собака - это животное, текстура - это экземпляр этого класса. Вы должны спросить себя, действительно ли это моделирует отношения между объектами в вашем коде. Дерево логического наследования очень ценно
Если дополнительный класс логически не вписывается в иерархию типов, вы можете использовать агрегирование. Это означает, что вы добавляете переменную экземпляра типа этого класса к общему суперклассу Sprite, Texture, Layer, ... Затем вы можете получить доступ к переменной с помощью ее получателя / установщика во всех подклассах. Это моделирует «Имеет - отношения».
Вы также можете преобразовать свой класс в интерфейс. Тогда вы могли бы расширить интерфейс со всеми своими классами, но должны были бы правильно реализовать методы в каждом классе. Это означает некоторую избыточность кода, но в данном случае небольшую.
Вы должны решить для себя, какой подход вам больше нравится. Лично я бы рекомендовал преобразовать класс в интерфейс.
Один совет: Typescript предлагает свойства, которые являются синтаксическим сахаром для геттеров и сеттеров. Возможно, вы захотите взглянуть на это: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/
источник
PIXI
и я не могу изменить библиотеку, чтобы добавить еще один класс поверх нее. 2) Это одно из возможных решений, которое я мог бы использовать, но я бы предпочел избегать его, если это возможно. 3) Я категорически не хочу дублировать этот код, теперь это может быть просто, но что будет дальше? Я работал над этой программой всего день, а позже добавлю еще много, не очень хорошее решение для меня. Я посмотрю на подсказку, спасибо за подробный ответ.PIXI
сам по себе не класс, это модуль, его нельзя расширять, но вы правы, это было бы жизнеспособным вариантом, если бы так было!TypeScript поддерживает декораторы , и, используя эту функцию плюс небольшую библиотеку под названием typescript-mix, вы можете использовать миксины для множественного наследования всего с парой строк.
// 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; }
источник
Я думаю, что есть гораздо лучший подход, который обеспечивает надежную безопасность типов и масштабируемость.
Сначала объявите интерфейсы, которые вы хотите реализовать в своем целевом классе:
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 = Base as any) { return class extends constructor implements IBar { public doBarThings() { console.log("Do bar!"); } }; } function Bazz<T extends Constructor>(constructor: T = Base as any) { return class extends constructor implements IBazz { public doBazzThings() { console.log("Do bazz!"); } }; }
Расширьте
Foo
класс с помощью миксинов класса:class Foo extends Bar(Bazz()) 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
источник
Очень хакерским решением было бы перебрать класс, который вы хотите унаследовать, добавляя одну за другой функции к новому родительскому классу.
class ChildA { public static x = 5 } class ChildB { public static y = 6 } class Parent {} for (const property in ChildA) { Parent[property] = ChildA[property] } for (const property in ChildB) { Parent[property] = ChildB[property] } Parent.x // 5 Parent.y // 6
Все свойства
ChildA
иChildB
теперь доступны изParent
класса, однако они не будут распознаны, что означает, что вы получите такие предупреждения, какProperty 'x' does not exist on 'typeof Parent'
источник
Я нашел актуальное и не имеющее аналогов решение: https://www.npmjs.com/package/ts-mixer
источник
Здесь уже так много хороших ответов, но я просто хочу показать на примере, что вы можете добавить дополнительную функциональность в расширяемый класс;
function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { if (name !== 'constructor') { derivedCtor.prototype[name] = baseCtor.prototype[name]; } }); }); } class Class1 { doWork() { console.log('Working'); } } class Class2 { sleep() { console.log('Sleeping'); } } class FatClass implements Class1, Class2 { doWork: () => void = () => { }; sleep: () => void = () => { }; x: number = 23; private _z: number = 80; get z(): number { return this._z; } set z(newZ) { this._z = newZ; } saySomething(y: string) { console.log(`Just saying ${y}...`); } } applyMixins(FatClass, [Class1, Class2]); let fatClass = new FatClass(); fatClass.doWork(); fatClass.saySomething("nothing"); console.log(fatClass.x);
источник
В паттернах проектирования есть принцип, называемый «предпочтение композиции перед наследованием». В нем говорится, что вместо наследования класса B от класса A поместите экземпляр класса A внутри класса B как свойство, а затем вы можете использовать функции класса A внутри класса B. Вы можете увидеть некоторые примеры этого здесь и здесь .
источник