Typescript: iki sınıf nasıl uzatılır?


90

Zamanımı kazanmak ve PIXI sınıflarını genişleten sınıflar arasında ortak kodu yeniden kullanmak istiyorum (bir 2d webGl oluşturucu kitaplığı).

Nesne Arayüzleri:

module Game.Core {
    export interface IObject {}

    export interface IManagedObject extends IObject{
        getKeyInManager(key: string): string;
        setKeyInManager(key: string): IObject;
    }
}

Sorunum kod içeriden olmasıdır getKeyInManagerve setKeyInManagerdeğişmeyecek ve ben bunu çoğaltmak için, onu yeniden kullanmak istiyorsanız, buraya uygulamasıdır:

export class ObjectThatShouldAlsoBeExtended{
    private _keyInManager: string;

    public getKeyInManager(key: string): string{
        return this._keyInManager;
    }

    public setKeyInManager(key: string): DisplayObject{
        this._keyInManager = key;
        return this;
    }
}

Ne yapmak istiyorum otomatik aracılığıyla, eklemektir Manager.add(), nesneye başvuruda yöneticisinde kullanılan anahtar içine kendi özelliğinde nesnenin kendisi _keyInManager.

Öyleyse, Texture ile bir örnek alalım. İşte gidiyorTextureManager

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);
        }
    }
}

Bunu yaptığımda this.add(), Game.Managers.Manager add()yöntemin döndürdüğü nesnede var olan bir yöntemi çağırmasını istiyorum Game.Core.Texture.fromImage("/" + relativePath). Bu nesne, bu durumda bir Texture:

module Game.Core {
    // I must extends PIXI.Texture, but I need to inject the methods in IManagedObject.
    export class Texture extends PIXI.Texture {

    }
}

Bunun IManagedObjectbir arayüz olduğunu ve uygulama içeremeyeceğini biliyorum, ancak sınıfımın ObjectThatShouldAlsoBeExtendediçindeki sınıfı enjekte etmek için ne yazacağımı bilmiyorum Texture. Aynı süreç için gerekli olacağını bilerek Sprite, TilingSprite,Layer ve daha fazlası.

Burada deneyimli bir TypeScript geri bildirimine / tavsiyesine ihtiyacım var, bunu yapmak mümkün olmalı, ancak birden fazla uzantıyla değil, o anda yalnızca bir tane mümkün olduğu için başka bir çözüm bulamadım.


7
Sadece bir ipucu, ne zaman çoklu bir miras sorunuyla karşılaşsam, kendime "miras yerine kompozisyonu tercih etmenin" işi yapıp yapmayacağını görmek için kendime hatırlatmaya çalışıyorum.
bubbleking

2
Kabul. 2 yıl önce böyle düşünmemiştim;)
Vadorequest

6
@bubbleking, kompozisyonu mirasa tercih etmek burada nasıl geçerli olur?
Seanny123

Yanıtlar:


97

TypeScript'te yeniden kullanılabilir küçük nesneler oluşturmak için Mixins'i kullanmanıza izin veren az bilinen bir özellik vardır. Bunları birden çok kalıtım kullanarak daha büyük nesneler halinde oluşturabilirsiniz (sınıflar için birden çok kalıtıma izin verilmez, ancak ilişkili bir yayılmaya sahip arabirimler gibi olan karışımlar için izin verilir).

TypeScript Karışımları hakkında daha fazla bilgi

Bu tekniği, oyununuzdaki birçok sınıf arasında ortak bileşenleri paylaşmak ve bu bileşenlerin çoğunu oyununuzda tek bir sınıftan yeniden kullanmak için kullanabileceğinizi düşünüyorum:

İşte hızlı bir Mixins demosu ... ilk olarak, karıştırmak istediğiniz tatlar:

class CanEat {
    public eat() {
        alert('Munch Munch.');
    }
}

class CanSleep {
    sleep() {
        alert('Zzzzzzz.');
    }
}

Ardından, Mixin oluşturma için sihirli yöntem (buna programınızda yalnızca bir kez ihtiyacınız olacak ...)

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    }); 
}

Ve sonra, mixin aromalarından çoklu mirasa sahip sınıflar oluşturabilirsiniz:

class Being implements CanEat, CanSleep {
        eat: () => void;
        sleep: () => void;
}
applyMixins (Being, [CanEat, CanSleep]);

Bu sınıfta gerçek bir uygulama olmadığına dikkat edin - "arayüzlerin" gereksinimlerini karşılaması için yeterlidir. Ama bu sınıfı kullandığımızda - hepsi işe yarıyor.

var being = new Being();

// Zzzzzzz...
being.sleep();

3
İşte typescript El Kitabı'nda Katmalar bölüm var (ama Steve hemen hemen Bu yanıtla biliyorum ve onun bağlantılı makalede gereken örtülü) typescriptlang.org/Handbook#mixins
Troy GIZZI

2
Typescript 2.2 artık
Mixins'i

1
@FlavienVolken Microsoft'un el kitabı belgelerinde eski mixins bölümünü neden sakladığını biliyor musunuz? Bu arada, benim gibi TS'de yeni başlayanlar için sürüm notlarını anlamak gerçekten zor. TS 2.2+ karışımları ile öğretici için herhangi bir bağlantı var mı? Teşekkürler.
David D.

4
Bu örnekte gösterilen mixin yapmanın "eski yolu", "yeni yoldan" daha basittir (Typescript 2.2+). Bunu neden bu kadar zorlaştırdıklarını bilmiyorum.
tocqueville

2
Nedeni, "eski yol" tipini doğru bir şekilde alamamasıdır.
unional

25

Orada açıklanan yeni mixins yaklaşımını kullanmanızı öneririm: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/

Bu yaklaşım, Fenton tarafından açıklanan "applyMixins" yaklaşımından daha iyidir, çünkü otomatik derleyici size yardımcı olur ve hem temel hem de 2. kalıtım sınıflarından tüm yöntemleri / özellikleri gösterir.

Bu yaklaşım TS Playground sitesinde kontrol edilebilir .

İşte uygulama:

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();

Kasıtlı olarak SecondInheritanceClasstanımlanmadı veya bir şeyi mi kaçırıyorum? Bu kodu TS oyun alanına yüklerken diyor expecting =>. Son olarak, addSecondInheritanceişlevde tam olarak neler olup bittiğini , örneğin amacının ne olduğunu parçalayabilir misiniz new (...args)?
Seanny123

Bu tür mixins uygulamasının ana noktası, her iki sınıfın TÜM yöntemlerinin ve özelliklerinin otomatik tamamlama IDE yardımında gösterilmesidir. İsterseniz 2. sınıfı tanımlayabilir ve Fenton tarafından önerilen yaklaşımı kullanabilirsiniz, ancak bu durumda IDE otomatik tamamlama çalışmayacaktır. {new (... args)} - bu kod, bir sınıf olması gereken bir nesneyi açıklar (TS arayüzleri hakkında daha fazla bilgi için el kitabını okuyabilirsiniz: typescriptlang.org/docs/handbook/interfaces.html
Mark Dolbyrev

2
Buradaki sorun, TS'nin değiştirilmiş sınıf hakkında hala bir ipucu olmamasıdır. Yazabiliyorum secondInheritanceObj.some()ve uyarı mesajı almıyorum.
sf

Karışık sınıfın arabirimlere uyup uymadığı nasıl kontrol edilir?
rilut

11
Typescript'ten gelen bu 'yeni mixins yaklaşımı' sonradan akla gelen bir şey gibi görünüyor. Bir geliştirici olarak sadece "Bu sınıfın ClassA ve ClassB'yi miras almasını istiyorum" veya "Bu sınıfın ClassA ve ClassB'nin bir karışımı olmasını istiyorum" diyebilmek istiyorum ve bunu hatırlayabildiğim net sözdizimiyle ifade etmek istiyorum 6 ayda, o saçma jumbo değil. TS derleyicisinin olanaklarının teknik bir kısıtlamasıysa öyle olsun, ama bu bir çözüm değil.
Rui Marques

12

Maalesef typcript çoklu mirası desteklemiyor. Bu nedenle tamamen önemsiz bir cevap yok, muhtemelen programınızı yeniden yapılandırmanız gerekecek

İşte birkaç öneri:

  • Bu ek sınıf, alt sınıflarınızın çoğunun paylaştığı davranışları içeriyorsa, bunu sınıf hiyerarşisine en üstte bir yere eklemek mantıklıdır. Belki de bu sınıftan Sprite, Texture, Layer ... gibi ortak üst sınıfları türetebilirsiniz? Tip hirarşisinde iyi bir yer bulabilirseniz, bu iyi bir seçim olacaktır. Ancak bu sınıfı rastgele bir noktaya eklemenizi tavsiye etmem. Kalıtım bir "ilişkidir" ifadesini ifade eder; örneğin, köpek bir hayvandır, doku bu sınıfın bir örneğidir. Bunun kodunuzdaki nesneler arasındaki ilişkiyi gerçekten modelleyip modellemediğini kendinize sormanız gerekir. Mantıksal bir miras ağacı çok değerlidir

  • Ek sınıf, tür hiyerarşisine mantıksal olarak uymuyorsa, toplamayı kullanabilirsiniz. Bu, bu sınıfın türünden bir örnek değişkenini, Sprite, Texture, Layer, ... gibi ortak bir üst sınıfına eklediğiniz anlamına gelir. Ardından, değişkene tüm alt sınıflarda alıcı / ayarlayıcı ile erişebilirsiniz. Bu bir "İlişkisi var" modelini oluşturur.

  • Ayrıca sınıfınızı bir arayüze dönüştürebilirsiniz. Daha sonra arayüzü tüm sınıflarınızla genişletebilirsiniz, ancak yöntemleri her sınıfta doğru şekilde uygulamanız gerekir. Bu, bir miktar kod fazlalığı anlamına gelir, ancak bu durumda çok fazla değildir.

En çok hangi yaklaşımı seveceğinize kendiniz karar vermelisiniz. Şahsen, sınıfı bir arayüze dönüştürmenizi tavsiye ederim.

Bir ipucu: Typescript, alıcılar ve ayarlayıcılar için sözdizimsel şeker olan özellikler sunar. Şuna bir göz atmak isteyebilirsiniz: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/


2
İlginç. 1) Bunu yapamam, çünkü genişletiyorum PIXIve üzerine başka bir sınıf eklemek için kitaplığı değiştiremiyorum. 2) Kullanabileceğim olası çözümlerden biri, ancak mümkünse bundan kaçınmayı tercih ederdim. 3) Kesinlikle bu kodu kopyalamak istemiyorum, şimdi basit olabilir, ama sonra ne olacak? Bu program üzerinde sadece bir gün çalıştım ve çok daha fazlasını ekleyeceğim, benim için iyi bir çözüm değil. İpucuna bir göz atacağım, ayrıntılı cevap için teşekkürler.
Vadorequest

Gerçekten ilginç bir bağlantı, ancak burada sorunu çözmek için herhangi bir kullanım durumu görmüyorum.
Vadorequest

Bu sınıfların tümü PIXI'yi genişletirse, yalnızca ObjectThatShouldAlsoBeExtended sınıfının PIXI'yi genişletmesini sağlayın ve bundan Texture, Sprite, ... Sınıflarını türetin. Sınıfı tür
hirarşisine eklemekle kastettiğim buydu

PIXIkendisi bir sınıf değil, bu bir modül, genişletilemez, ama haklısın, eğer öyle olsaydı bu uygulanabilir bir seçenek olurdu!
Vadorequest

1
Yani sınıfların her biri PIXI modülünden başka bir sınıfı mı genişletiyor? O zaman haklısınız, ObjectThatShouldAlso sınıfını tür hirarşisine ekleyemezsiniz. Bu mümkün değil. Hala bir ilişkiyi veya arayüzü seçebilirsiniz. Tüm sınıflarınızın paylaştığı ortak bir davranışı tanımlamak istediğiniz için, bir arayüz kullanmanızı tavsiye ederim. Kod yinelense bile en temiz tasarım budur.
lhk

12

TypeScript, dekoratörleri destekler ve bu özelliği ve ayrıca typcript -mix adı verilen küçük bir kitaplığı kullanarak, yalnızca birkaç satırla birden çok kalıtıma sahip olmak için mixin'leri kullanabilirsiniz.

// 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;
}

8

Sağlam tip güvenliğine izin veren çok daha iyi bir yaklaşım olduğunu düşünüyorum. ve ölçeklenebilirliğe .

Öncelikle hedef sınıfınıza uygulamak istediğiniz arayüzleri açıklayın:

interface IBar {
  doBarThings(): void;
}

interface IBazz {
  doBazzThings(): void;
}

class Foo implements IBar, IBazz {}

Şimdi uygulamayı Foosınıfa eklemeliyiz . Bu arayüzleri de uygulayan sınıf karışımlarını kullanabiliriz:

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!");
    }
  };
}

FooSınıfı sınıf karışımlarıyla genişletin :

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

1
Bar ve Bazz işlevlerinin döndürülen türü nedir?
Luke Skywalker

3

Yeni üst sınıfa işlevleri tek tek ekleyerek miras almak istediğiniz sınıfta döngü yapmak çok zor bir çözüm olacaktır.

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

Sınıfın tüm özelliklerine ChildAve ChildBartık Parentsınıftan erişilebilir , ancak bunlar tanınmayacak, bu da aşağıdaki gibi uyarılar alacağınız anlamına gelir:Property 'x' does not exist on 'typeof Parent'



0

Burada zaten pek çok iyi cevap var, ancak sadece genişletilmekte olan sınıfa ek işlevler ekleyebileceğinizi bir örnekle göstermek istiyorum;

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);

0

Tasarım modellerinde "kompozisyonu mirasa tercih etme" adı verilen bir ilke vardır. B Sınıfını A Sınıfından miras almak yerine, B sınıfının içine bir özellik olarak A sınıfının bir örneğini koyun ve ardından B sınıfının içindeki A sınıfının işlevlerini kullanabileceğinizi söyler.Bunun bazı örneklerini burada ve burada görebilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.