Chcę zaoszczędzić czas i ponownie używać wspólnego kodu między klasami, który rozszerza klasy PIXI (biblioteka renderująca 2d webGl).
Interfejsy obiektów:
module Game.Core {
export interface IObject {}
export interface IManagedObject extends IObject{
getKeyInManager(key: string): string;
setKeyInManager(key: string): IObject;
}
}
Moim problemem jest to, że kod wewnątrz getKeyInManager
i setKeyInManager
nie ulegnie zmianie i chcę go ponownie wykorzystać, a nie powielać, oto implementacja:
export class ObjectThatShouldAlsoBeExtended{
private _keyInManager: string;
public getKeyInManager(key: string): string{
return this._keyInManager;
}
public setKeyInManager(key: string): DisplayObject{
this._keyInManager = key;
return this;
}
}
Chcę automatycznie dodać Manager.add()
klucz używany w menedżerze do odniesienia się do obiektu wewnątrz samego obiektu w jego właściwościach _keyInManager
.
Weźmy więc przykład z teksturą. Tutaj idzieTextureManager
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);
}
}
}
Kiedy to zrobię this.add()
, chcę, aby Game.Managers.Manager
add()
metoda wywoływała metodę, która istniałaby w obiekcie zwróconym przez Game.Core.Texture.fromImage("/" + relativePath)
. Tym obiektem w tym przypadku byłby Texture
:
module Game.Core {
// I must extends PIXI.Texture, but I need to inject the methods in IManagedObject.
export class Texture extends PIXI.Texture {
}
}
Wiem, że IManagedObject
jest to interfejs i nie może zawierać implementacji, ale nie wiem, co napisać, aby wstrzyknąć klasę ObjectThatShouldAlsoBeExtended
do mojej Texture
klasy. Wiedząc, że sam proces będzie wymagana Sprite
, TilingSprite
, Layer
i więcej.
Potrzebuję tutaj opinii / porad doświadczonych w języku TypeScript, musi to być możliwe, ale nie przez wiele rozszerzeń, ponieważ w tym momencie możliwy jest tylko jeden, nie znalazłem żadnego innego rozwiązania.
źródło
Odpowiedzi:
W języku TypeScript jest mało znana funkcja, która umożliwia używanie mikserów do tworzenia małych obiektów wielokrotnego użytku. Możesz komponować je w większe obiekty przy użyciu wielokrotnego dziedziczenia (wielokrotne dziedziczenie nie jest dozwolone w przypadku klas, ale jest dozwolone w przypadku miksów - które są jak interfejsy z powiązaną implementacją).
Więcej informacji o mieszankach TypeScript
Myślę, że możesz użyć tej techniki, aby udostępnić wspólne komponenty wielu klasom w twojej grze i ponownie użyć wielu z tych komponentów z jednej klasy w twojej grze:
Oto szybkie demo Mixins ... najpierw smaki, które chcesz mieszać:
class CanEat { public eat() { alert('Munch Munch.'); } } class CanSleep { sleep() { alert('Zzzzzzz.'); } }
Następnie magiczna metoda tworzenia Mixinów (potrzebujesz tego tylko raz gdzieś w swoim programie ...)
function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { if (name !== 'constructor') { derivedCtor.prototype[name] = baseCtor.prototype[name]; } }); }); }
Następnie możesz tworzyć klasy z wielokrotnym dziedziczeniem po mieszanych smakach:
class Being implements CanEat, CanSleep { eat: () => void; sleep: () => void; } applyMixins (Being, [CanEat, CanSleep]);
Zauważ, że nie ma rzeczywistej implementacji w tej klasie - wystarczy, aby spełniała wymagania "interfejsów". Ale kiedy używamy tej klasy - wszystko działa.
var being = new Being(); // Zzzzzzz... being.sleep();
źródło
Proponuję skorzystać z opisanego tam nowego podejścia do miksów: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/
To podejście jest lepsze niż podejście „applyMixins” opisane przez Fentona, ponieważ autokompilator pomoże ci i pokaże wszystkie metody / właściwości z podstawowej i drugiej klasy dziedziczenia.
To podejście można sprawdzić w witrynie TS Playground .
Oto realizacja:
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();
źródło
SecondInheritanceClass
niezdefiniowane celowo albo ja czegoś brakuje? Podczas ładowania tego kodu do placu zabaw TS, mówiexpecting =>
. Na koniec, czy możesz dokładnie określić, co dzieje się waddSecondInheritance
funkcji, na przykład jaki jest jej celnew (...args)
?secondInheritanceObj.some()
i nie otrzymuję komunikatu ostrzegawczego.Niestety maszynopis nie obsługuje wielokrotnego dziedziczenia. Dlatego nie ma całkowicie trywialnej odpowiedzi, prawdopodobnie będziesz musiał przebudować swój program
Oto kilka sugestii:
Jeśli ta dodatkowa klasa zawiera zachowanie wspólne dla wielu twoich podklas, sensowne jest umieszczenie jej w hierarchii klas, gdzieś na górze. Może mógłbyś wyprowadzić wspólną superklasę Sprite, Texture, Layer, ... z tej klasy? Byłby to dobry wybór, jeśli możesz znaleźć dobre miejsce w hirarchii typów. Ale nie polecałbym po prostu wstawiać tej klasy w przypadkowym miejscu. Dziedziczenie wyraża „Jest - zależnością”, np. Pies jest zwierzęciem, tekstura jest przykładem tej klasy. Należy zadać sobie pytanie, czy to naprawdę modeluje relacje między obiektami w kodzie. Logiczne drzewo dziedziczenia jest bardzo cenne
Jeśli dodatkowa klasa nie pasuje logicznie do hierarchii typów, można użyć agregacji. Oznacza to, że dodajesz zmienną instancji typu tej klasy do wspólnej nadklasy Sprite, Texture, Layer, ... Następnie możesz uzyskać dostęp do zmiennej za pomocą jej metody pobierającej / ustawiającej we wszystkich podklasach. To modeluje „ma - związek”.
Możesz także przekształcić swoją klasę w interfejs. Następnie możesz rozszerzyć interfejs o wszystkie swoje klasy, ale musiałbyś poprawnie zaimplementować metody w każdej klasie. Oznacza to pewną redundancję kodu, ale w tym przypadku niewiele.
Musisz sam zdecydować, które podejście lubisz najbardziej. Osobiście poleciłbym przekonwertować klasę na interfejs.
Jedna wskazówka: Typescript oferuje właściwości, które są cukrem syntaktycznym dla metod pobierających i ustawiających. Możesz rzucić okiem na to: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/
źródło
PIXI
i nie mogę zmienić biblioteki, aby dodać do niej kolejną klasę. 2) Jest to jedno z możliwych rozwiązań, których mógłbym użyć, ale wolałbym go unikać, jeśli to możliwe. 3) Zdecydowanie nie chcę powielać tego kodu, teraz może to być proste, ale co będzie dalej? Pracowałem nad tym programem tylko jeden dzień i później dodam dużo więcej, co nie jest dla mnie dobrym rozwiązaniem. Rzucę okiem na wskazówkę, dzięki za szczegółową odpowiedź.PIXI
Samo w sobie nie jest klasą, jest modułem, nie można go rozszerzyć, ale masz rację, to byłaby realna opcja, gdyby była!TypeScript obsługuje dekoratory i używając tej funkcji oraz małej biblioteki zwanej mieszanką maszynopisu , możesz użyć kombinacji, aby mieć wielokrotne dziedziczenie za pomocą zaledwie kilku linii
// 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; }
źródło
Myślę, że jest znacznie lepsze podejście, które pozwala na solidne bezpieczeństwo typów i skalowalność.
Najpierw zadeklaruj interfejsy, które chcesz zaimplementować w swojej klasie docelowej:
interface IBar { doBarThings(): void; } interface IBazz { doBazzThings(): void; } class Foo implements IBar, IBazz {}
Teraz musimy dodać implementację do
Foo
klasy. Możemy użyć mixinów klas, które również implementują następujące interfejsy: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!"); } }; }
Rozszerz
Foo
klasę o mieszanki klas: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
źródło
Bardzo zmyślnym rozwiązaniem byłoby przechodzenie przez klasę, którą chcesz odziedziczyć, po dodaniu funkcji jedna po drugiej do nowej klasy nadrzędnej
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
Wszystkie właściwości
ChildA
iChildB
są teraz dostępne z poziomuParent
klasy, jednak nie zostaną rozpoznane, co oznacza, że otrzymasz ostrzeżenia, takie jakProperty 'x' does not exist on 'typeof Parent'
źródło
We wzorcach projektowych obowiązuje zasada zwana „przedkładaniem kompozycji nad dziedziczenie”. Mówi, że zamiast dziedziczyć klasę B z klasy A, umieść instancję klasy A w klasie B jako właściwość, a następnie możesz użyć funkcji klasy A w klasie B. Możesz zobaczyć kilka przykładów tutaj i tutaj .
źródło
Znalazłem aktualne i niezrównane rozwiązanie: https://www.npmjs.com/package/ts-mixer
źródło
Jest tu już tak wiele dobrych odpowiedzi, ale chcę tylko pokazać na przykładzie, że można dodać dodatkowe funkcje do rozszerzanej klasy;
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);
źródło