Tapuscrit: Comment étendre deux classes?
Je souhaite gagner du temps et réutiliser du code commun à toutes les classes, ce qui étend les classes PIXI (une bibliothèque de rendu WebGl 2d).
Interfaces d'objets:
module Game.Core {
export interface IObject {}
export interface IManagedObject extends IObject{
getKeyInManager(key: string): string;
setKeyInManager(key: string): IObject;
}
}
Mon problème est que le code à l'intérieur de getKeyInManager
et setKeyInManager
ne changera pas et je veux le réutiliser, pas pour le dupliquer, voici l'implémentation:
export class ObjectThatShouldAlsoBeExtended{
private _keyInManager: string;
public getKeyInManager(key: string): string{
return this._keyInManager;
}
public setKeyInManager(key: string): DisplayObject{
this._keyInManager = key;
return this;
}
}
Ce que je veux faire, c’est d’ajouter automatiquement, par le biais d’une Manager.add()
, la clé utilisée dans le gestionnaire pour référencer l’objet à l’intérieur du s'objecte dans sa propriété _keyInManager
.
Alors, prenons un exemple avec une texture. Voici le 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);
}
}
}
Quand je fais this.add()
, je veux que la méthode Game.Managers.Manager
add()
appelle une méthode qui existerait sur l'objet renvoyé par Game.Core.Texture.fromImage("/" + relativePath)
. Cet objet, dans ce cas, serait un Texture
:
module Game.Core {
// I must extends PIXI.Texture, but I need to inject the methods in IManagedObject.
export class Texture extends PIXI.Texture {
}
}
Je sais que IManagedObject
est une interface et ne peut pas contenir d’implémentation, mais je ne sais pas quoi écrire pour injecter la classe ObjectThatShouldAlsoBeExtended
dans ma classe Texture
. Sachant que le même processus serait requis pour Sprite
, TilingSprite
, Layer
et plus.
J'ai besoin d'un retour d'expérience/conseils TypeScript ici, il doit être possible de le faire, mais pas par plusieurs extensions, car un seul est possible à la fois, je n'ai trouvé aucune autre solution.
Il existe une fonctionnalité peu connue dans TypeScript qui vous permet d’utiliser Mixins pour créer de petits objets réutilisables. Vous pouvez les composer en objets plus volumineux en utilisant plusieurs héritages (l'héritage multiple n'est pas autorisé pour les classes, mais il est autorisé pour les mixins, qui sont comme des interfaces avec une implémentation associée).
Plus d'informations sur les mixins TypeScript
Je pense que vous pouvez utiliser cette technique pour partager des composants communs entre plusieurs classes de votre jeu et pour réutiliser plusieurs de ces composants d'une classe unique dans votre jeu:
Voici une démo rapide de Mixins ... tout d’abord, les saveurs que vous souhaitez mélanger:
class CanEat {
public eat() {
alert('Munch Munch.');
}
}
class CanSleep {
sleep() {
alert('Zzzzzzz.');
}
}
Ensuite, la méthode magique pour la création Mixin (vous n’en avez besoin qu’une fois, quelque part dans votre programme ...)
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
if (name !== 'constructor') {
derivedCtor.prototype[name] = baseCtor.prototype[name];
}
});
});
}
Et vous pouvez ensuite créer des classes avec plusieurs héritages à partir de saveurs mixin:
class Being implements CanEat, CanSleep {
eat: () => void;
sleep: () => void;
}
applyMixins (Being, [CanEat, CanSleep]);
Notez qu'il n'y a pas d'implémentation réelle dans cette classe - juste assez pour lui faire passer les exigences des "interfaces". Mais lorsque nous utilisons cette classe, tout fonctionne.
var being = new Being();
// Zzzzzzz...
being.sleep();
Je suggérerais d'utiliser la nouvelle approche de mixins décrite ici: https://blogs.msdn.Microsoft.com/TypeScript/2017/02/22/announcing-TypeScript-2-2/
Cette approche est meilleure que l'approche "applyMixins" décrite par Fenton, car le compilateur automatique vous aide et affiche toutes les méthodes/propriétés des classes de base et d'héritage.
Cette approche peut être vérifiée sur le site TS Playground .
Voici l'implémentation:
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();
Malheureusement, TypeScript ne supporte pas l'héritage multiple. Par conséquent, il n’ya pas de réponse complètement triviale, vous devrez probablement restructurer votre programme.
Voici quelques suggestions:
Si cette classe supplémentaire contient un comportement que partagent plusieurs de vos sous-classes, il est logique de l'insérer dans la hiérarchie des classes, quelque part en haut. Peut-être que vous pourriez tirer la super-classe commune de Sprite, Texture, Layer, ... de cette classe? Ce serait un bon choix si vous pouvez trouver une bonne place dans le type hiérarchique. Mais je ne recommanderais pas simplement d'insérer cette classe à un moment donné. L'héritage exprime une "Est une - relation" par ex. un chien est un animal, une texture est un exemple de cette classe. Vous devez vous demander si cela modélise réellement la relation entre les objets de votre code. Un arbre d'héritage logique est très précieux
Si la classe supplémentaire ne correspond pas logiquement à la hiérarchie des types, vous pouvez utiliser l'agrégation. Cela signifie que vous ajoutez une variable d'instance du type de cette classe à une super-classe commune de Sprite, Texture, Layer, ... Vous pouvez ensuite accéder à la variable avec son getter/setter dans toutes les sous-classes. Ceci modélise un "a une - relation".
Vous pouvez également convertir votre classe en une interface. Ensuite, vous pouvez étendre l'interface à toutes vos classes, mais vous devrez implémenter les méthodes correctement dans chaque classe. Cela signifie une certaine redondance du code mais dans ce cas pas grand chose.
Vous devez décider vous-même quelle approche vous préférez. Personnellement, je recommanderais de convertir la classe en une interface.
Un conseil: TypeScript offre des propriétés, qui sont du sucre syntaxique pour les accesseurs et les setters. Vous voudrez peut-être jeter un coup d'œil à ceci: http://blogs.Microsoft.co.il/gilf/2013/01/22/creating-properties-in-TypeScript/
Je pense qu’il existe une approche bien meilleure, qui permet sécurité de type solide et évolutive.
Commencez par déclarer les interfaces que vous souhaitez implémenter sur votre classe cible:
interface IBar {
doBarThings(): void;
}
interface IBazz {
doBazzThings(): void;
}
class Foo implements IBar, IBazz {}
Nous devons maintenant ajouter l'implémentation à la classe Foo
. Nous pouvons utiliser des mixins de classes qui implémentent également ces interfaces:
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!");
}
};
}
Étendez la classe Foo
avec les classes mixins:
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
Il y a une nouvelle fonctionnalité dans JavaScript (ES7) appelée décorateurs, et en utilisant cette fonctionnalité plus une petite bibliothèque appelée TypeScript-mix vous pouvez utiliser mixins pour avoir plusieurs héritages avec seulement quelques lignes.
// 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;
}