Eu quero economizar meu tempo e reutilizar o código comum entre as classes que estende as classes PIXI (uma biblioteca de renderizador 2d webGl).
Interfaces de objeto:
module Game.Core {
export interface IObject {}
export interface IManagedObject extends IObject{
getKeyInManager(key: string): string;
setKeyInManager(key: string): IObject;
}
}
Meu problema é que o interior de código getKeyInManager
e setKeyInManager
não vai mudar e eu quero reutilizá-lo, não duplicá-lo, aqui é a implementação:
export class ObjectThatShouldAlsoBeExtended{
private _keyInManager: string;
public getKeyInManager(key: string): string{
return this._keyInManager;
}
public setKeyInManager(key: string): DisplayObject{
this._keyInManager = key;
return this;
}
}
O que quero fazer é adicionar automaticamente, por meio de a Manager.add()
, a chave usada no gerenciador para fazer referência ao objeto dentro do próprio objeto em sua propriedade _keyInManager
.
Então, vamos dar um exemplo com uma textura. Aqui vai oTextureManager
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);
}
}
}
Quando eu fizer isso this.add()
, quero que o Game.Managers.Manager
add()
método chame um método que existiria no objeto retornado por Game.Core.Texture.fromImage("/" + relativePath)
. Este objeto, neste caso, seria um Texture
:
module Game.Core {
// I must extends PIXI.Texture, but I need to inject the methods in IManagedObject.
export class Texture extends PIXI.Texture {
}
}
Eu sei que IManagedObject
é uma interface e não pode conter implementação, mas não sei o que escrever para injetar a classe ObjectThatShouldAlsoBeExtended
dentro da minha Texture
classe. Sabendo que seria necessário o mesmo processo para Sprite
, TilingSprite
, Layer
e muito mais.
Preciso de um feedback / conselhos experientes do TypeScript aqui, deve ser possível fazer isso, mas não por extensões múltiplas, pois apenas uma é possível no momento, não encontrei nenhuma outra solução.
fonte
Respostas:
Há um recurso pouco conhecido no TypeScript que permite usar Mixins para criar pequenos objetos reutilizáveis. Você pode compor estes em objetos maiores usando herança múltipla (herança múltipla não é permitida para classes, mas é permitida para mixins - que são como interfaces com uma implementação associada).
Mais informações sobre TypeScript Mixins
Acho que você poderia usar essa técnica para compartilhar componentes comuns entre muitas classes em seu jogo e reutilizar muitos desses componentes de uma única classe em seu jogo:
Aqui está uma rápida demonstração dos Mixins ... primeiro, os sabores que você deseja misturar:
class CanEat { public eat() { alert('Munch Munch.'); } } class CanSleep { sleep() { alert('Zzzzzzz.'); } }
Em seguida, o método mágico para a criação do Mixin (você só precisa disso uma vez em algum lugar do seu 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]; } }); }); }
E então você pode criar classes com herança múltipla de sabores mixin:
class Being implements CanEat, CanSleep { eat: () => void; sleep: () => void; } applyMixins (Being, [CanEat, CanSleep]);
Observe que não há implementação real nesta classe - apenas o suficiente para fazê-la passar nos requisitos das "interfaces". Mas quando usamos esta classe - tudo funciona.
var being = new Being(); // Zzzzzzz... being.sleep();
fonte
Eu sugeriria usar a nova abordagem de mixins descrita lá: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/
Essa abordagem é melhor do que a abordagem "applyMixins" descrita por Fenton, porque o autocompiler o ajudaria e mostraria todos os métodos / propriedades das classes de herança base e segunda.
Essa abordagem pode ser verificada no site TS Playground .
Aqui está a implementação:
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();
fonte
SecondInheritanceClass
está definido de propósito ou estou faltando alguma coisa? Ao carregar este código no playground do TS, ele dizexpecting =>
. Por fim, você poderia analisar exatamente o que está acontecendo naaddSecondInheritance
função, como qual é o propósito denew (...args)
?secondInheritanceObj.some()
e não obtenho uma mensagem de aviso.Infelizmente, o typescript não suporta herança múltipla. Portanto, não há uma resposta completamente trivial, você provavelmente terá que reestruturar seu programa
Aqui estão algumas sugestões:
Se essa classe adicional contém o comportamento que muitas de suas subclasses compartilham, faz sentido inseri-la na hierarquia de classes, em algum lugar no topo. Talvez você pudesse derivar a superclasse comum de Sprite, Texture, Layer, ... desta classe? Esta seria uma boa escolha, se você puder encontrar um bom local na hierarquia de tipos. Mas eu não recomendaria apenas inserir essa classe em um ponto aleatório. Herança expressa um "É um relacionamento", por exemplo, um cachorro é um animal, uma textura é uma instância desta classe. Você teria que se perguntar se isso realmente modela o relacionamento entre os objetos em seu código. Uma árvore de herança lógica é muito valiosa
Se a classe adicional não se encaixar logicamente na hierarquia de tipo, você pode usar a agregação. Isso significa que você adiciona uma variável de instância do tipo desta classe a uma superclasse comum de Sprite, Texture, Layer, ... Então você pode acessar a variável com seu getter / setter em todas as subclasses. Isso modela um "Tem um - relacionamento".
Você também pode converter sua classe em uma interface. Então, você poderia estender a interface com todas as suas classes, mas teria que implementar os métodos corretamente em cada classe. Isso significa alguma redundância de código, mas, neste caso, não muito.
Você tem que decidir por si mesmo qual abordagem você mais gosta. Pessoalmente, eu recomendaria converter a classe em uma interface.
Uma dica: o typescript oferece propriedades, que são açúcar sintático para getters e setters. Você pode querer dar uma olhada em: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/
fonte
PIXI
e não posso alterar a biblioteca para adicionar outra classe em cima dela. 2) É uma das soluções possíveis que eu poderia usar, mas teria preferido evitá-la, se possível. 3) Definitivamente, não quero duplicar esse código, pode ser simples agora, mas o que vai acontecer a seguir? Trabalhei neste programa apenas um dia e adicionarei muito mais depois, o que não é uma boa solução para mim. Vou dar uma olhada na dica, obrigado pela resposta detalhada.PIXI
em si não é uma classe, é um módulo, não pode ser estendido, mas você está certo, teria sido uma opção viável se fosse!TypeScript suporta decoradores , e usando esse recurso mais uma pequena biblioteca chamada typescript-mix você pode usar mixins para ter herança múltipla com apenas algumas linhas
// 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; }
fonte
Acho que há uma abordagem muito melhor, que permite segurança de tipo sólida e escalabilidade.
Primeiro, declare as interfaces que deseja implementar em sua classe de destino:
interface IBar { doBarThings(): void; } interface IBazz { doBazzThings(): void; } class Foo implements IBar, IBazz {}
Agora temos que adicionar a implementação à
Foo
classe. Podemos usar mixins de classes que também implementam essas 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!"); } }; }
Estenda a
Foo
aula com os mixins de aula: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
fonte
Uma solução muito hacky seria fazer um loop pela classe que você deseja herdar ao adicionar as funções uma por uma à nova classe pai
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 as propriedades de
ChildA
eChildB
agora podem ser acessadas a partir daParent
classe, no entanto, elas não serão reconhecidas, o que significa que você receberá avisos comoProperty 'x' does not exist on 'typeof Parent'
fonte
Encontrei uma solução atualizada e incomparável: https://www.npmjs.com/package/ts-mixer
fonte
Já existem tantas respostas boas aqui, mas eu só quero mostrar com um exemplo que você pode adicionar funcionalidade adicional à classe que está sendo estendida;
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);
fonte
Nos padrões de projeto, existe um princípio denominado "favorecer a composição em vez da herança". Diz em vez de herdar a Classe B da Classe A, coloque uma instância da classe A dentro da classe B como uma propriedade e então você pode usar as funcionalidades da classe A dentro da classe B. Você pode ver alguns exemplos disso aqui e aqui .
fonte