web-dev-qa-db-ja.com

Typescript:2つのクラスを拡張するには?

時間を節約し、PIXIクラス(2D webGlレンダラーライブラリ)を拡張するクラス全体で共通のコードを再利用したいと思います。

オブジェクトインターフェイス:

module Game.Core {
    export interface IObject {}

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

私の問題は、getKeyInManagerおよびsetKeyInManager内のコードは変更されず、複製するのではなく再利用したいということです。ここに実装があります。

export class ObjectThatShouldAlsoBeExtended{
    private _keyInManager: string;

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

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

私がやりたいのは、オブジェクトを参照するためにマネージャーで使用されるキーをManager.add()を介して自動的に追加することですinsideプロパティ_keyInManagerで。

それでは、テクスチャの例を見てみましょう。 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);
        }
    }
}

this.add()を実行するとき、Game.Managers.Manageradd()メソッドに、Game.Core.Texture.fromImage("/" + relativePath)によって返されるオブジェクトに存在するメソッドを呼び出すようにしたい。このオブジェクトは、この場合はTextureです:

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

    }
}

IManagedObjectはインターフェイスであり、実装を含めることができないことは知っていますが、ObjectThatShouldAlsoBeExtendedクラス内にTextureクラスを挿入するために何を書くべきかわかりません。 SpriteTilingSpriteLayerなどにも同じプロセスが必要であることを知っています。

ここで経験豊富なTypeScriptフィードバック/アドバイスが必要です、それを行うことが可能でなければなりませんが、一度に1つしか可能でないため、複数の拡張ではできません。他の解決策は見つかりませんでした。

51
Vadorequest

TypeScriptには、Mixinsを使用して再利用可能な小さなオブジェクトを作成できる、あまり知られていない機能があります。多重継承を使用して、これらをより大きなオブジェクトに構成できます(多重継承はクラスでは許可されませんが、ミックスインでは許可されます-これは、関連する実装を含むインターフェイスのようなものです)。

TypeScript Mixinsの詳細

このテクニックを使用して、ゲーム内の多くのクラス間で共通のコンポーネントを共有し、ゲーム内の単一のクラスからこれらのコンポーネントの多くを再利用できると思います。

ここに簡単なMixinsデモがあります...最初に、ミックスしたいフレーバー:

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

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

次に、Mixinを作成するための魔法の方法(これはプログラムのどこかで一度だけ必要です...)

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

そして、mixinフレーバーから複数の継承を持つクラスを作成できます。

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

このクラスには実際の実装はないことに注意してください-「インターフェース」の要件を通過させるのに十分です。しかし、このクラスを使用すると、すべて機能します。

var being = new Being();

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

そこで説明されている新しいミックスインアプローチを使用することをお勧めします。 https://blogs.msdn.Microsoft.com/TypeScript/2017/02/22/announcing-TypeScript-2-2/

このアプローチは、Fentonによって記述された「applyMixins」アプローチよりも優れています。これは、オートコンパイラがあなたを助け、ベースクラスと2番目の継承クラスのすべてのメソッド/プロパティを表示するためです。

このアプローチは TS Playgroundサイト で確認できます。

実装は次のとおりです。

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();
15
Mark Dolbyrev

残念ながら、TypeScriptは多重継承をサポートしていません。したがって、完全に些細な答えはありません。おそらくプログラムを再構築する必要があります。

以下にいくつかの提案を示します。

  • この追加のクラスに多くのサブクラスが共有する動作が含まれている場合、クラス階層の最上部のどこかに挿入することは理にかなっています。このクラスから、スプライト、テクスチャ、レイヤーなどの共通のスーパークラスを派生させることができますか?タイプ階層で良い場所を見つけることができれば、これは良い選択でしょう。しかし、このクラスをランダムな位置に挿入することはお勧めしません。継承は、「関係」を表します。犬は動物であり、テクスチャはこのクラスのインスタンスです。これにより、コード内のオブジェクト間の関係が実際にモデル化されているかどうかを自問する必要があります。論理継承ツリーは非常に貴重です

  • 追加のクラスが論理的に型階層に収まらない場合は、集計を使用できます。つまり、このクラスのタイプのインスタンス変数をSprite、Texture、Layerなどの共通のスーパークラスに追加すると、すべてのサブクラスのゲッター/セッターで変数にアクセスできます。これは、「Has a-relationship」をモデル化しています。

  • クラスをインターフェイスに変換することもできます。次に、すべてのクラスでインターフェイスを拡張できますが、各クラスでメソッドを正しく実装する必要があります。これはコードの冗長性を意味しますが、この場合はそれほど重要ではありません。

どのアプローチが一番好きかを自分で決める必要があります。個人的には、クラスをインターフェイスに変換することをお勧めします。

1つのヒント:TypeScriptはプロパティを提供します。これはゲッターとセッターの構文糖衣です。あなたはこれを見てみたいかもしれません: http://blogs.Microsoft.co.il/gilf/2013/01/22/creating-properties-in-TypeScript/

8
lhk

solid type-safetyとスケーラビリティを可能にする、はるかに優れたアプローチがあると思います。

最初に、ターゲットクラスに実装するインターフェイスを宣言します。

interface IBar {
  doBarThings(): void;
}

interface IBazz {
  doBazzThings(): void;
}

class Foo implements IBar, IBazz {}

次に、実装をFooクラスに追加する必要があります。これらのインターフェイスも実装するクラスミックスインを使用できます。

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

クラスミックスインでFooクラスを拡張します。

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
2
nomadoda

JavaScript(ES7)にはデコレータと呼ばれる新しい機能があり、その機能と TypeScript-mix という小さなライブラリを使用すると、mixinを使用して数行で複数の継承を行うことができます

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