JavaScriptでinterfaceを使ったポリモーフィズムを表現する


以下の記事を参考にさせて頂きました。

要点

この記事は素のJavaScriptでJavaやC#でinterfaceを使うように、機能を限定したポリモーフィズムを表現する実装方法を自分なりに考えたものです。

もう少し詳しい要点

JavaScriptは変数に型という概念がないので、ポリモーフィズムの様な振る舞いは簡単に出来るんですが、逆に何でも出来すぎるので機能を限定したインターフェースで定義した型の変数に入っているようにしたいと思いました。
TypeScriptを使えばすむのですが、素のJavaScriptでどうにか定義して見たいと思いこの記事を書きました。

実装

RPGゲームを例にします。
戦闘中用のインターフェース(もどき)をBattlerクラスとして下記のように定義しました。
そしてこのBattlerクラスがこの記事のメインです。
JSはES6からclass構文が追加されましたが、interfaceはないのでinterfaceもどきです。
この記事の中ではこのBattlerクラスを「Battlerインターフェース」と呼ぶことにします。

Battler.js
class Battler {
    constructor(party) {
        this.party = party;
    }
    attack() {
        this.party.attack();
    }
}

それではこのBattlerインターフェースをつかってみます。
下記は勇者を表すHeroクラスと魔法使いを表すWizardクラスです。
Javaであればインターフェースはimplementsしますが、出来ないのでextendsでBattlerインターフェースを継承しています。
(しかし、この記事内の実装は継承しなくても出力は変わりません。)

rpg.js
class Hero extends Battler{

    attack() {
        console.log('勇者の攻撃!');
    }
    speak() {
        console.log('勇者「Hello」');
    }
}

class Wizard extends Battler{
    attack() {
        console.log('魔法使いの攻撃!');
    }
    speak() {
        console.log('魔法使い「Hello」');
    }
}

上記クラスの説明

HeroクラスとWizardクラス両方に定義しているattackメソッドはバトル中の物理攻撃
同じく両方に定義しているspeakメソッドはフィールド移動中にキャラクターに話しかけるメソッドです。
バトル中はこのspeakメソッドを呼び出せない仕様にしたいです。
そして、その実装が下記です。

rpg.js
//--------バトル開始--------
const hero = new Hero();
const wizard = new Wizard();
const battlers = [ //ポリモーフィズムの恩恵をうけるため配列に格納
    new Battler(hero), //Battlerインターフェース型に変換
    new Battler(wizard) //Battlerインターフェース型に変換
];

// attackメソッドを呼び出す
for (const chara of battlers) {
    chara.attack(); // 呼び出せる
}
//勇者の攻撃!
//魔法使いの攻撃!


// speakメソッドを呼び出す(Battlerインターフェースからは呼び出せない)
for (const chara of battlers) {
    chara.speak(); // エラー 呼び出せない
}

今回の実装は趣味で作ってるRPGゲームの実装に使っていこうと思います。
もっといい実装方法などありましたらコメント頂ければと思います。