Mixins

イントロダクション

伝統的なOO(オブジェクト指向)階層と並び、 再利用可能なコンポーネントからクラスを構築するもう一つの方法に、より単純な部分クラスを組み合わせる方法が挙げられます。

これはScalaのような言語のMixinや特性の考え方に通じるものがあり、 JavaScriptコミュニティでもこのパターンが人気を博しています。

Mixinのサンプル

以下のコードでは、TypeScriptでmixinをモデル化する方法を示しています。 コードの後に、どのように動作するのかを分析してみましょう。

// 使い捨て(Disposable)Mixin
class Disposable {
    isDisposed: boolean;
    dispose() {
        this.isDisposed = true;
    }

}

// 活性化可能な(Activatable)Mixin
class Activatable {
    isActive: boolean;
    activate() {
        this.isActive = true;
    }
    deactivate() {
        this.isActive = false;
    }
}

class SmartObject implements Disposable, Activatable {
    constructor() {
        setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
    }

    interact() {
        this.activate();
    }

    // Disposable
    isDisposed: boolean = false;
    dispose: () => void;
    // Activatable
    isActive: boolean = false;
    activate: () => void;
    deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]);

let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);

////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////

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

サンプルの解説

サンプルは、Mixinとして機能する2つのクラスから始まります。 それぞれが特定の活動や能力に焦点を当てているのが分かります。 後でそれらを組み合わせて、両方の機能から新しいクラスを作成します。

// Disposable Mixin
class Disposable {
    isDisposed: boolean;
    dispose() {
        this.isDisposed = true;
    }

}

// Activatable Mixin
class Activatable {
    isActive: boolean;
    activate() {
        this.isActive = true;
    }
    deactivate() {
        this.isActive = false;
    }
}

次に、2つのMixinの組み合わせを処理するクラスを作成します。 これをどのように行うかを詳しく見てみましょう。

class SmartObject implements Disposable, Activatable {

上のコードで最初に気付くことは、extendsではなくimplementsを使用することでしょう。 これらのクラスをインタフェースとして扱い、実装というよりはDisposableActivatableの背後にある型だけを使用します。

つまり、クラス内で実装を提供する必要があります。 それ以外はMixinを使って避けたいものです。

この要件を満たすために、Mixinによって取り込まれるメンバ用の身代わり(stand-in)プロパティとその型を用意します。 これによって、コンパイラは実行時にこれらのメンバを使用できるようになります。 多少、コードを余分に書かなければいけませんが、Mixinの恩恵が受けられます。

// Disposable
isDisposed: boolean = false;
dispose: () => void;
// Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;

最終的に、Mixinをクラスに混ぜて完全な実装を作り出します。

applyMixins(SmartObject, [Disposable, Activatable]);

最後に、Mixinを行うヘルパー関数を作成します。 これは、各Mixinのプロパティが実行され、Mixinの対象にコピーされ、 それらの実装が身代わり(stand-in)プロパティに埋められていきます。

 Back to top

© https://github.com/Microsoft/TypeScript-Handbook

このページは、ページトップのリンク先のTypeScript-Handbook内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • ドキュメントの情報が古い可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。