Quiero ahorrar tiempo y reutilizar código común en todas las clases que amplía las clases PIXI (una biblioteca de renderizado webGl 2d).
Interfaces de objetos:
module Game.Core {
export interface IObject {}
export interface IManagedObject extends IObject{
getKeyInManager(key: string): string;
setKeyInManager(key: string): IObject;
}
}
Mi problema es que el código dentro getKeyInManager
y setKeyInManager
no cambiará y quiero reutilizarlo, no duplicarlo, aquí está la implementación:
export class ObjectThatShouldAlsoBeExtended{
private _keyInManager: string;
public getKeyInManager(key: string): string{
return this._keyInManager;
}
public setKeyInManager(key: string): DisplayObject{
this._keyInManager = key;
return this;
}
}
Lo que quiero hacer es agregar automáticamente, a través de a Manager.add()
, la clave utilizada en el administrador para hacer referencia al objeto dentro del propio objeto en su propiedad _keyInManager
.
Entonces, tomemos un ejemplo con una textura. Aquí va elTextureManager
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);
}
}
}
Cuando lo haga this.add()
, quiero que el Game.Managers.Manager
add()
método llame a un método que existiría en el objeto devuelto por Game.Core.Texture.fromImage("/" + relativePath)
. Este objeto, en este caso sería 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 {
}
}
Sé que IManagedObject
es una interfaz y no puede contener implementación, pero no sé qué escribir para inyectar la clase ObjectThatShouldAlsoBeExtended
dentro de mi Texture
clase. Sabiendo que sería necesario el mismo proceso para Sprite
, TilingSprite
, Layer
y mucho más.
Necesito comentarios / consejos de TypeScript con experiencia aquí, debe ser posible hacerlo, pero no mediante múltiples extensiones, ya que solo es posible una en ese momento, no encontré ninguna otra solución.
fuente
Respuestas:
Hay una característica poco conocida en TypeScript que le permite usar Mixins para crear pequeños objetos reutilizables. Puede componerlos en objetos más grandes utilizando herencia múltiple (la herencia múltiple no está permitida para las clases, pero está permitida para los mixins, que son como interfaces con una implementación asociada).
Más información sobre Mixins de TypeScript
Creo que podrías usar esta técnica para compartir componentes comunes entre muchas clases en tu juego y reutilizar muchos de estos componentes de una sola clase en tu juego:
Aquí hay una demostración rápida de Mixins ... primero, los sabores que desea mezclar:
class CanEat { public eat() { alert('Munch Munch.'); } } class CanSleep { sleep() { alert('Zzzzzzz.'); } }
Luego, el método mágico para la creación de Mixin (solo lo necesita una vez en algún lugar de su programa ...)
function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { if (name !== 'constructor') { derivedCtor.prototype[name] = baseCtor.prototype[name]; } }); }); }
Y luego puede crear clases con herencia múltiple de sabores mixtos:
class Being implements CanEat, CanSleep { eat: () => void; sleep: () => void; } applyMixins (Being, [CanEat, CanSleep]);
Tenga en cuenta que no existe una implementación real en esta clase, solo lo suficiente para que pase los requisitos de las "interfaces". Pero cuando usamos esta clase, todo funciona.
var being = new Being(); // Zzzzzzz... being.sleep();
fuente
Sugeriría usar el nuevo enfoque de mixins que se describe allí: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/
Este enfoque es mejor que el enfoque "applyMixins" descrito por Fenton, porque el autocompilador le ayudaría y le mostraría todos los métodos / propiedades de las clases base y segunda herencia.
Este enfoque se puede verificar en el sitio de TS Playground .
Aquí está la implementación:
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();
fuente
SecondInheritanceClass
No está definido a propósito o me falta algo? Al cargar este código en el patio de juegos de TS, diceexpecting =>
. Finalmente, ¿podría desglosar exactamente lo que está sucediendo en laaddSecondInheritance
función, por ejemplo, cuál es el propósitonew (...args)
?secondInheritanceObj.some()
y no recibo un mensaje de advertencia.Lamentablemente, el mecanografiado no admite la herencia múltiple. Por lo tanto, no hay una respuesta completamente trivial, probablemente tendrá que reestructurar su programa
Aqui hay algunas sugerencias:
Si esta clase adicional contiene un comportamiento que comparten muchas de sus subclases, tiene sentido insertarlo en la jerarquía de clases, en algún lugar en la parte superior. ¿Quizás podrías derivar la superclase común de Sprite, Texture, Layer, ... de esta clase? Esta sería una buena opción, si puede encontrar un buen lugar en la jerarquía de tipos. Pero no recomendaría simplemente insertar esta clase en un punto aleatorio. La herencia expresa una relación "Es una -", por ejemplo, un perro es un animal, una textura es una instancia de esta clase. Tendrías que preguntarte si esto realmente modela la relación entre los objetos en tu código. Un árbol de herencia lógica es muy valioso
Si la clase adicional no encaja lógicamente en la jerarquía de tipos, puede utilizar la agregación. Eso significa que agrega una variable de instancia del tipo de esta clase a una superclase común de Sprite, Texture, Layer, ... Luego puede acceder a la variable con su getter / setter en todas las subclases. Esto modela un "Tiene una relación".
También puede convertir su clase en una interfaz. Luego, podría extender la interfaz con todas sus clases, pero tendría que implementar los métodos correctamente en cada clase. Esto significa cierta redundancia de código, pero en este caso no mucha.
Tienes que decidir por ti mismo qué enfoque te gusta más. Personalmente, recomendaría convertir la clase en una interfaz.
Un consejo: TypeScript ofrece propiedades, que son azúcar sintáctico para getters y setters. Es posible que desee echar un vistazo a esto: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/
fuente
PIXI
y no puedo cambiar la biblioteca para agregar otra clase encima. 2) Es una de las posibles soluciones que podría usar, pero hubiera preferido evitarla si fuera posible. 3) Definitivamente no quiero duplicar ese código, puede ser simple ahora, pero ¿qué pasará después? He trabajado en este programa solo un día y agregaré mucho más más adelante, no es una buena solución para mí. Echaré un vistazo a la sugerencia, gracias por la respuesta detallada.PIXI
en sí mismo no es una clase, es un módulo, no se puede extender, pero tienes razón, ¡habría sido una opción viable si lo fuera!TypeScript admite decoradores , y usando esa función más una pequeña biblioteca llamada mecanografiado-mix , puede usar mixins para tener herencia múltiple con solo un par de líneas
// 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; }
fuente
Creo que hay un enfoque mucho mejor, que permite una escalabilidad y seguridad de tipos sólidas .
Primero declare las interfaces que desea implementar en su clase de destino:
interface IBar { doBarThings(): void; } interface IBazz { doBazzThings(): void; } class Foo implements IBar, IBazz {}
Ahora tenemos que agregar la implementación a la
Foo
clase. Podemos usar class mixins que también implemente estas 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!"); } }; }
Amplíe la
Foo
clase con los mixins de la clase: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
fuente
Una solución muy intrincada sería recorrer la clase que desea heredar al agregar las funciones una por una a la nueva clase principal
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
Todas las propiedades de
ChildA
yChildB
ahora se puede acceder desde laParent
clase, sin embargo, no se reconocerán, lo que significa que recibirá advertencias comoProperty 'x' does not exist on 'typeof Parent'
fuente
En los patrones de diseño existe un principio llamado "favorecer la composición sobre la herencia". Dice que en lugar de heredar la Clase B de la Clase A, coloque una instancia de la clase A dentro de la clase B como una propiedad y luego puede usar funcionalidades de la clase A dentro de la clase B. Puede ver algunos ejemplos de eso aquí y aquí .
fuente
Encontré una solución actualizada e incomparable: https://www.npmjs.com/package/ts-mixer
fuente
Ya hay tantas buenas respuestas aquí, pero solo quiero mostrar con un ejemplo que puede agregar funcionalidad adicional a la clase que se está extendiendo;
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);
fuente