Ich möchte meine Zeit sparen und allgemeinen Code klassenübergreifend wiederverwenden, wodurch PIXI-Klassen (eine 2D-WebGl-Renderer-Bibliothek) erweitert werden.
Objektschnittstellen:
module Game.Core {
export interface IObject {}
export interface IManagedObject extends IObject{
getKeyInManager(key: string): string;
setKeyInManager(key: string): IObject;
}
}
Mein Problem ist, dass sich der Code darin befindet getKeyInManager
und setKeyInManager
sich nicht ändert und ich ihn wiederverwenden und nicht duplizieren möchte. Hier ist die Implementierung:
export class ObjectThatShouldAlsoBeExtended{
private _keyInManager: string;
public getKeyInManager(key: string): string{
return this._keyInManager;
}
public setKeyInManager(key: string): DisplayObject{
this._keyInManager = key;
return this;
}
}
Was ich tun möchte, ist, automatisch über a Manager.add()
den Schlüssel hinzuzufügen, der im Manager verwendet wird, um das Objekt innerhalb des Objekts selbst in seiner Eigenschaft zu referenzieren _keyInManager
.
Nehmen wir also ein Beispiel mit einer Textur. Hier geht dasTextureManager
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);
}
}
}
Wenn ich dies tue this.add()
, möchte ich, dass die Game.Managers.Manager
add()
Methode eine Methode aufruft, die für das von zurückgegebene Objekt vorhanden ist Game.Core.Texture.fromImage("/" + relativePath)
. Dieses Objekt wäre in diesem Fall ein Texture
:
module Game.Core {
// I must extends PIXI.Texture, but I need to inject the methods in IManagedObject.
export class Texture extends PIXI.Texture {
}
}
Ich weiß, dass dies IManagedObject
eine Schnittstelle ist und keine Implementierung enthalten kann, aber ich weiß nicht, was ich schreiben soll, um die Klasse ObjectThatShouldAlsoBeExtended
in meine Texture
Klasse einzufügen . Zu wissen , dass der gleiche Prozess für erforderlich wäre Sprite
, TilingSprite
, Layer
und vieles mehr.
Ich benötige hier ein erfahrenes TypeScript-Feedback / Ratschläge, es muss möglich sein, aber nicht in mehreren Schritten, da zu diesem Zeitpunkt nur eines möglich ist. Ich habe keine andere Lösung gefunden.
quelle
Antworten:
In TypeScript gibt es eine wenig bekannte Funktion, mit der Sie Mixins verwenden können, um wiederverwendbare kleine Objekte zu erstellen. Sie können diese mithilfe der Mehrfachvererbung zu größeren Objekten zusammensetzen (Mehrfachvererbung ist für Klassen nicht zulässig, Mixins jedoch - wie Schnittstellen mit einer zugehörigen Implenentation).
Weitere Informationen zu TypeScript Mixins
Ich denke, Sie könnten diese Technik verwenden, um gemeinsame Komponenten zwischen vielen Klassen in Ihrem Spiel zu teilen und viele dieser Komponenten aus einer einzelnen Klasse in Ihrem Spiel wiederzuverwenden:
Hier ist eine kurze Mixins-Demo ... zuerst die Aromen, die Sie mischen möchten:
class CanEat { public eat() { alert('Munch Munch.'); } } class CanSleep { sleep() { alert('Zzzzzzz.'); } }
Dann die magische Methode zur Mixin-Erstellung (Sie brauchen diese nur einmal irgendwo in Ihrem Programm ...)
function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { if (name !== 'constructor') { derivedCtor.prototype[name] = baseCtor.prototype[name]; } }); }); }
Und dann können Sie Klassen mit Mehrfachvererbung aus Mixin-Varianten erstellen:
class Being implements CanEat, CanSleep { eat: () => void; sleep: () => void; } applyMixins (Being, [CanEat, CanSleep]);
Beachten Sie, dass es in dieser Klasse keine tatsächliche Implementierung gibt - gerade genug, um die Anforderungen der "Schnittstellen" zu erfüllen. Aber wenn wir diese Klasse verwenden, funktioniert alles.
var being = new Being(); // Zzzzzzz... being.sleep();
quelle
Ich würde vorschlagen, den dort beschriebenen neuen Mixins-Ansatz zu verwenden: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/
Dieser Ansatz ist besser als der von Fenton beschriebene "applyMixins" -Ansatz, da der Autocompiler Ihnen helfen und alle Methoden / Eigenschaften sowohl der Basis- als auch der 2. Vererbungsklasse anzeigen würde.
Dieser Ansatz kann auf der TS Playground-Website überprüft werden .
Hier ist die Implementierung:
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();
quelle
SecondInheritanceClass
nicht absichtlich definiert oder fehlt mir etwas? Beim Laden dieses Codes auf den TS-Spielplatz heißt esexpecting =>
. Könnten Sie schließlich genau aufschlüsseln, was in deraddSecondInheritance
Funktion passiert , beispielsweise was ist der Zweck vonnew (...args)
?secondInheritanceObj.some()
und bekomme keine Warnmeldung.Leider unterstützt Typoskript keine Mehrfachvererbung. Daher gibt es keine völlig triviale Antwort, Sie müssen wahrscheinlich Ihr Programm umstrukturieren
Hier einige Vorschläge:
Wenn diese zusätzliche Klasse ein Verhalten enthält, das viele Ihrer Unterklassen gemeinsam haben, ist es sinnvoll, es irgendwo oben in die Klassenhierarchie einzufügen. Vielleicht könnten Sie aus dieser Klasse die übliche Oberklasse von Sprite, Texture, Layer, ... ableiten? Dies wäre eine gute Wahl, wenn Sie einen guten Platz in der Typ-Hirarchie finden könnten. Ich würde jedoch nicht empfehlen, diese Klasse nur an einer zufälligen Stelle einzufügen. Vererbung drückt eine "Ist eine - Beziehung" aus, zB ein Hund ist ein Tier, eine Textur ist eine Instanz dieser Klasse. Sie müssten sich fragen, ob dies wirklich die Beziehung zwischen den Objekten in Ihrem Code modelliert. Ein logischer Vererbungsbaum ist sehr wertvoll
Wenn die zusätzliche Klasse nicht logisch in die Typhierarchie passt, können Sie die Aggregation verwenden. Das bedeutet, dass Sie eine Instanzvariable vom Typ dieser Klasse zu einer allgemeinen Oberklasse von Sprite, Texture, Layer, ... hinzufügen. Anschließend können Sie in allen Unterklassen auf die Variable mit ihrem Getter / Setter zugreifen. Dies modelliert eine "Hat eine - Beziehung".
Sie können Ihre Klasse auch in eine Schnittstelle konvertieren. Dann könnten Sie die Schnittstelle mit all Ihren Klassen erweitern, müssten aber die Methoden in jeder Klasse korrekt implementieren. Dies bedeutet eine gewisse Code-Redundanz, in diesem Fall jedoch nicht viel.
Sie müssen selbst entscheiden, welcher Ansatz Ihnen am besten gefällt. Persönlich würde ich empfehlen, die Klasse in eine Schnittstelle zu konvertieren.
Ein Tipp: Typescript bietet Eigenschaften, die syntaktischer Zucker für Getter und Setter sind. Vielleicht möchten Sie einen Blick darauf werfen: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/
quelle
PIXI
und die Bibliothek nicht ändern kann, um eine weitere Klasse darüber hinzuzufügen. 2) Es ist eine der möglichen Lösungen, die ich verwenden könnte, aber ich hätte es vorgezogen, wenn möglich zu vermeiden. 3) Ich möchte diesen Code definitiv nicht duplizieren, es mag jetzt einfach sein, aber was wird als nächstes passieren? Ich habe nur einen Tag an diesem Programm gearbeitet und ich werde später noch viel mehr hinzufügen, keine gute Lösung für mich. Ich werde mir den Tipp ansehen, danke für die detaillierte Antwort.PIXI
selbst ist keine Klasse, es ist ein Modul, es kann nicht erweitert werden, aber Sie haben Recht, das wäre eine praktikable Option gewesen, wenn es gewesen wäre!TypeScript unterstützt Dekoratoren . Mit dieser Funktion und einer kleinen Bibliothek namens Typoskript-Mix können Sie Mixins verwenden, um mit nur wenigen Zeilen mehrere Vererbungen durchzuführen
// 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; }
quelle
Ich denke, es gibt einen viel besseren Ansatz, der solide Typensicherheit und Skalierbarkeit ermöglicht.
Deklarieren Sie zunächst Schnittstellen, die Sie in Ihrer Zielklasse implementieren möchten:
interface IBar { doBarThings(): void; } interface IBazz { doBazzThings(): void; } class Foo implements IBar, IBazz {}
Jetzt müssen wir die Implementierung zur
Foo
Klasse hinzufügen . Wir können Klassenmixins verwenden, die auch diese Schnittstellen implementieren: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!"); } }; }
Erweitern Sie die
Foo
Klasse mit den Klassenmixins: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
quelle
Eine sehr hackige Lösung wäre, die Klasse zu durchlaufen, die Sie erben möchten, indem Sie die Funktionen einzeln zur neuen übergeordneten Klasse hinzufügen
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
Alle Eigenschaften von
ChildA
undChildB
können jetzt von derParent
Klasse aus aufgerufen werden. Sie werden jedoch nicht erkannt, was bedeutet, dass Sie Warnungen wie zProperty 'x' does not exist on 'typeof Parent'
quelle
In Entwurfsmustern gibt es ein Prinzip namens "Komposition gegenüber Vererbung bevorzugen". Anstatt Klasse B von Klasse A zu erben, wird eine Instanz der Klasse A in die Klasse B als Eigenschaft eingefügt. Anschließend können Sie die Funktionen der Klasse A in der Klasse B verwenden. Hier und hier finden Sie einige Beispiele dafür .
quelle
Ich habe eine aktuelle und beispiellose Lösung gefunden: https://www.npmjs.com/package/ts-mixer
quelle
Es gibt hier bereits so viele gute Antworten, aber ich möchte nur anhand eines Beispiels zeigen, dass Sie der Klasse, die erweitert wird, zusätzliche Funktionen hinzufügen können.
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);
quelle