poo - prototipos en javascript
Mecanografiado: ¿Cómo extender dos clases? (3)
Hay una pequeña característica conocida en TypeScript que le permite usar Mixins para crear objetos pequeños 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 mixins, que son como interfaces con una implementación asociada).
Más información sobre TypeScript Mixins
Creo que podrías utilizar esta técnica para compartir componentes comunes entre muchas clases en tu juego y para reutilizar muchos de estos componentes de una sola clase en tu juego:
Aquí hay una demo rápida de Mixins ... primero, los sabores que quieres 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 necesitas esto una vez en algún lugar de tu 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 puedes crear clases con herencia múltiple a partir de los sabores de mixin:
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();
Quiero ahorrar mi tiempo y reutilizar el código común en todas las clases que amplían las clases PIXI (una biblioteca de renderizador 2d webGl).
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 de 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 Manager.add()
, la clave utilizada en el administrador para hacer referencia al objeto dentro del objeto en su propiedad _keyInManager
.
Entonces, tomemos un ejemplo con una Textura. Aquí va el 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);
}
}
}
Cuando hago this.add()
, quiero que el Game.Managers.Manager
add()
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 una 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 la implementación, pero no sé qué escribir para inyectar la clase ObjectThatShouldAlsoBeExtended
dentro de mi clase Texture
. Sabiendo que se requeriría el mismo proceso para Sprite
, TilingSprite
, Layer
y más.
Necesito comentarios / sugerencias de TypeScript con experiencia aquí, debe ser posible hacerlo, pero no por extensiones múltiples ya que solo uno es posible en ese momento, no encontré ninguna otra solución.
Lamentablemente, el texto mecanografiado no admite 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 de la parte superior. ¿Tal vez 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 hirarquía tipo. Pero no recomendaría simplemente insertar esta clase en un punto aleatorio. La herencia expresa una "relación Is-a", por ejemplo, un perro es un animal, una textura es una instancia de esta clase. Debería preguntarse si esto realmente modela la relación entre los objetos en su 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 usar la agregación. Eso significa que agrega una variable de instancia del tipo de esta clase a una superclase común de Sprite, Textura, Capa, ... Luego puede acceder a la variable con su getter / setter en todas las subclases. Esto modela una "Tiene una relación".
También podría convertir su clase en una interfaz. Entonces podría extender la interfaz con todas sus clases, pero tendría que implementar los métodos correctamente en cada clase. Esto significa un poco de redundancia de código, pero en este caso no mucho.
Tienes que decidir por ti mismo qué enfoque te gusta más. Personalmente, recomendaría convertir la clase a 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/
Sugeriría usar el nuevo enfoque de mixins descrito 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, ya que el autocompilador lo ayudaría y mostraría todos los métodos / propiedades de las clases de herencia tanto de base como de segunda.
Este enfoque podría verificarse en el sitio 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();