Chapter 08

第八章 interface を使った型定義とポリモーフィズム

kuramapommel
kuramapommel
2021.04.23に更新

interface もオブジェクト指向プログラミング言語では当たり前のように用意されている機能です
interface はここでは 「型を定義するための機能」 としておきましょう

LongSword class , DualBlades class , Bow class はそれぞれ、武器としての振る舞いを持つ class です
つまり、これらの class を使う側からすると 「武器」を使うと表現することができます

「太刀」や「双剣」を使うではなく、「武器」を使うと言う表現は、 「太刀」や「双剣」の持つ「武器」と言う特徴を抽象的に捉えた表現になっている ことがわかるかと思います
ポリモーフィズムで重要な 抽象化 というのは、このように 物事の特徴を抽象的に捉えること を指します

では早速、 「武器 interface を定義してみましょう

// interface を使って Weapon interface を定義
interface Weapon {
  // 武器種名のプロパティ
  name: string
  
  // 攻撃のメソッド
  attack(): void
}

前述の通り interface は型という制約の定義でしかないので、 具体的な実装は持っていません
Weapon を使う人が、 Weapon という型は "攻撃する" というメソッドと、 "名前" というプロパティを持っている」 ことが分かれば十分だからです

使う人は具体的にどんな処理が行われるかは気にしません
気にすることは、 その振る舞いを起こした結果どうなるか だけです

現実世界で例えてみましょう
あなたが普段良く遊ぶゲームをイメージしてみてください

コントローラの左スティックを前に倒すと操作しているキャラクターが前に進む というのはなんとなくイメージできるかと思います
どんな処理が行われているかはわからないけど、とにかくキャラクターは前に進みますよね

中で行われている処理など気にせずともぼくらがゲームを楽しむためには、 左スティックを前に倒すと操作しているキャラクターが前に進むということだけ知っていれば良い のです

では、「 B ボタン」 はどうでしょう
ときに「 B ボタン」は

  • 「キャンセルする」という振る舞い を起こしたり
  • 「ジャンプする」という振る舞い を起こしたり
  • 「ダッシュする」という振る舞い を起こしたりします

同じ「 B ボタン」を押すという操作で異なる振る舞いが実現 していますね

interface というのは現実世界で言うところのゲームのコントローラのようなものです
決められた数のボタンしかありません( = 型という制約)が、「場合」に応じて異なる振る舞いを実現できます

すみません、話が長くなってしまいました
では、この Weapon に具体的な処理を実装してあげましょう

interface Weapon {
  name: string
  attack(): void
}

// implements を使って Bow が Weapon の具体的な処理を実装していることを明示
class Bow implements Weapon {

  public readonly name: string = "弓"
  public attack(): void {
    console.log("弓で攻撃")
  }
}

これを interface を実装する」 と呼びます
また、抽象的な interface を実装している class のことを 具象 class と呼びます

interface は型なので、前の章でコンパイルエラーとなってしまった箇所も次のように直すことができます

// C# のコードです

public sealed class Hello
{
    // Main 関数を省略
    
    // IWeapone interface を定義
    // C# は慣習として interface の場合 `I` という prefix を付けます
    private interface IWeapone {
        // 実装を持たない Name プロパティを定義
        string Name { get; }
    }
    
    // CreateWeaponInstance の戻りの型を IWeapon に変更
    private static IWeapone CreateWeaponInstance(WEAPON_TYPE weaponType) {
        switch (weaponType) {
            // それぞれの class は IWeapone を実装しているので、コンパイルエラーにならない
            case WEAPON_TYPE.LongSword: return new LongSword();
            
            case WEAPON_TYPE.DualBlades: return new DualBlades();
            
            case WEAPON_TYPE.Bow: return new Bow();
        }
        
        throw new ArgumentOutOfRangeException();
    }

    // それぞれの class が IWeapone interface を実装
    // C# では implements の代わりに `:` を使います
    private sealed class LongSword : IWeapone {
        public string Name => "太刀";
    }
    
    private sealed class DualBlades : IWeapone {
        public string Name => "双剣";
    }
    
    private sealed class Bow : IWeapone {
        public string Name => "弓";
    }

    // enum 定義を省略
}

C# じゃなくて TypeScript で表すなら、下記のようになります
ここまでの総集編なので、ちょっとコード長いですが内容は把握できるかと思います

// Weapon interface を定義
interface Weapon {
  name: string
  attack(): void
}

// implements を使ってそれぞれのクラスが Weapon を実装
class LongSword implements Weapon {
  public readonly name: string = "太刀"
  public attack(): void {
    console.log("太刀で攻撃")
  }
}

class DualBlades implements Weapon {
  public readonly name: string = "双剣"
  public attack(): void {
    console.log("双剣で攻撃")
  }
}

class Bow implements Weapon {
  public readonly name: string = "弓"
  public attack(): void {
    console.log("弓で攻撃")
  }
}

enum WEAPON_TYPE {
  LONG_SWORD,
  DUAL_BLADES,
  BOW
}

// 渡された WEAPON_TYPE に応じた Weapon の具体的なインスタンスを生成する関数を定義
function createWeaponInstance(type: WEAPON_TYPE): Weapon {
  // 唯一の switch 
  // 分岐箇所をここひとつに纏めることができました
  switch (type) {
    case WEAPON_TYPE.LONG_SWORD:
      return new LongSword()

    case WEAPON_TYPE.DUAL_BLADES:
      return new DualBlades()

    case WEAPON_TYPE.BOW:
      return new Bow()
  }
  
  throw Error()
}

そして最後にこれを使ってみます

// 使いたい WEAPON_TYPE を用意
const weaponType = WEAPON_TYPE.BOW;

// 生成される具体的なインスタンスは意識せず
// weapon として抽象的に捉える
const weapon: Weapon = createWeaponInstance(weaponType)

// Weapon 型であれば attack のメソッドを使うことはできる
// ポリモーフィズムが効いているので、実際は具体的な class に実装した attack の振る舞いが起こる
weapon.attack()

// Weapon 型であれば武器種名も取得できる
// ポリモーフィズムが効いているので、実際は具体的な class に実装した name の振る舞いが起こる
const weaponName = weapon.name
console.log(weaponName)

はいできました、ポリモーフィズム
これがポリモーフィズムです