ポリモーフィズムを活用するとなぜ if や switch  が消えるのか？


突然ですが、プログラミングをしていてこんなコードに出くわしませんか？

```js
if (売上.勘定科目 == 勘定科目.現金) {
    // 計算するロジック
} else if (売上.勘定科目 == 勘定科目.売掛金) {
    // 計算するロジック
} else if (売上.勘定科目 == 勘定科目.有価証券) {
    // 計算するロジック
}
```



このようなコードはしばしば**スパゲッティ:spaghetti:**になりがちですし、
項目が増えるたびに、条件分岐を増やさないといけないので**保守も大変:sweat:**です。

売上の課目ごとに計算方法が違いますが金額計算するという振る舞いは同じです。
このコードから**`if`文を駆逐**するにはどうしたらいいでしょうか？

型やフラグ、`enum`による条件分岐はたいていの場合、**ポリモーフィズム**によって消し去ることができます。
ポリモーフィズムとは**異なる型のオブジェクトを同一視し、そのオブジェクトの型によって動作を切り替える**ことです。

ポリモーフィズムは動的型付け言語ではダックタイピング、
静的型付け言語ではインターフェースや抽象クラスで実現できます。

この記事では**『三角、円、四角がある。それらの面積を計算する。』**という例で考えてみたいと思います。

## **動的型付け言語で `if` や `switch` を消す例**

動的型付け言語は **JavaScript** を例に説明したいと思います。

### **手続き型による条件分岐のコード**

以下のように三角、円、四角の各図形を用意しました。
図形は三角、円、四角か見分けるために`shapeflag`を持ちます。

```js:app.js
const triangle = {
  shapeflag: "triangle",
  base: 5,
  height: 4
};

const circle = {
  shapeflag: "circle",
  radius: 3
};

const rectangle = {
  shapeflag: "rectangle",
  width: 4,
  height: 4
};
```

さて、ここから各図形の面積を計算する処理を実装する場合、どこに書いたらよいでしょうか？
とりあえず、図形を引き数に、図形のフラグによって計算方法を分岐するメソッドを書きました。

```js
function computeArea(shape) {
  switch (shape.shapeflag) {
    case "triangle":
      return shape.base * shape.height / 2;
    case "circle":
      return shape.radius * shape.radius * Math.PI;
    case "rectangle":
      return shape.width * shape.height;
    default:
      throw new Error();
  }
}
```

配列に三角、円、四角を入れて、順番に計算してみます。

```js
for (const shape of [triangle, circle, rectangle]) {
  console.log(computeArea(shape));
}
```

```
> node app.js
10
28.274333882308138
16
```

図形にあった計算方法が呼び出されました。
しかし、この方法では図形が増えるたびに`switch`文の`case`を増やさないといけません。

この程度だとさほど問題にもなりませんが、冒頭にあげたような例ですと、
プログラムの成長に従ってこれと**似たような条件分岐の構造**が繰り返し現れたり、
**巨大なユーティリティ**が出来上がったりしてどんどん複雑化していきます。

### **ダックタイピングによって条件分岐を消したコード**

ところで、オブジェクト指向設計の有名な原則に**『Don't ask, tell.』**というものがあります。
『求めるな、命じよ。』とか『聞くな、言え。』などと訳されます。
オブジェクトに尋ねるのではなく、命じなさいという意味です。

今回の例だと、
**『図形は何ですか？』**
**:neutral_face:『三角形です。』**
**『底辺と高さは何ですか』**
**:neutral_face:『底辺は `5` 、高さは `4` です』**
**『なら、面積は `5 × 4 ÷ 2` で `10` ですね。』**
ではなく、
**『面積を計算しなさい。』**
**:neutral_face:『`10` です。』**
になります。

ダックタイピングを活用し`switch`や`if`を消し去るには、
振る舞いが適切な場所に定義される必要があります。

図形共通の振る舞いは**面積を求められること**です。
**『Don't ask, tell.』**に従って図形自身に面積を計算させましょう。

```js
const triangle = {
  base: 5,
  height: 4,
  area: function() {
    return this.base * this.height / 2;
  }
};

const circle = {
  radius: 3,
  area: function() {
    return this.radius * this.radius * Math.PI;
  }
};

const rectangle = {
  width: 4,
  height: 4,
  area: function() {
    return this.width * this.height;
  }
};
```

三角、円、四角は`area`という共通のメソッド[^1]を持ちました。

配列に三角、円、四角を入れて、順番に面積を計算してみます。

```js
for (const shape of [triangle, circle, rectangle]) {
  console.log(shape.area());
}
```

```bash
> node app.js
10
28.274333882308138
16
```

きちんと計算できています。
そして、**`if`文やフラグは消えました。**


ここで大事なのは`shape`の`area`メソッドが実行されていますが、
**実際に呼び出されているのは三角、円、四角それぞれの`area`メソッド**[^1]であり、
同じ`area`メソッドでも動作が切り替わっていることです。

## **静的型付け言語で `if` や `switch` を消す例**

静的型付け言語は **C#** を例に説明したいと思います。

三角形、円、図形を定義しましょう。
同じように**『Don't ask, tell.』**に従って、図形自身に面積を計算させます。

```c#
class Triangle {
    public double Base { get; set; }
    public double Height { get; set; }

    public double Area() => Base * Height / 2;
}

class Circle {
    public double Radius { get; set; }

    public double Area() => Radius * Radius * Math.PI;
}

class Rectangle {
    public double Width { get; set; }
    public double Height { get; set; }

    public double Area() => Width * Height;
}
```
このまま三角、円、図形を面積を計算できるものとして統一して扱いたいところですが、
一般的な静的型付け言語はこのままではポリモーフィズムを実現できません。

面積を求められるという共通の振る舞いを抽象化した図形インターフェースを定義する必要があります。

```c#
interface IShape {
    double Area();
}
```

図形インターフェースを三角、円、四角に実装します。
今回は元から`Area`メソッドを持っているためクラスの内容に変化はありません。

```c#
class Triangle : IShape { /* 略 */ }

class Circle : IShape { /* 略 */ }

class Rectangle : IShape { /* 略 */ }
```

これで三角、円、四角は面積を求められる型（`IShape`）として統一的に扱えるようになりました。
実際に上記のクラスを使ったコードは以下のようになります。

```c#
static void Main() {
    IShape[] shapes = {
        new Triangle { Base = 5, Height = 4 },
        new Circle { Radius = 3 },
        new Rectangle { Width = 4, Height = 4 },
    };

    foreach (var shape in shapes) {
        Console.WriteLine($"AREA: {shape.Area()}");
    }
}
```

```powershell
> dotnet run
AREA: 10
AREA: 28.2743338823081
AREA: 16
```

インターフェースを定義するのが回りくどいように感じますが、
`Area`が実行できること100パーセント保証してくれたり、エディタの支援が強いなどのメリットもあります。

例えば、`IShape`を実装しない型が配列に入らないので実行時エラーを起こりませんし、
`Area`をタイポしてもリアルタイムでエラーを教えてくれたり、補完がゴリゴリ効きます。
また、面積を計算できる型として振舞えるかどうかユニットテストする（ダックテスト）必要もなくなります。

### **静的型付けでもダックタイピングできる言語**

静的型付け言語でも **TypeScript** や **Golang** などは一歩進んだダックタイピングが可能です。
TypeScript は複数の型を統一的に扱った場合、**共用体型`Union Type`**として扱われます。[^2]
Golang はある`interface`の定義を全て満たす構造体は**暗黙的にその`interface`を実装している**ことになります。
参考：[golangでダックタイピングをしてみよう ](http://blog.engineer.adways.net/entry/2017/07/14/120000)

Golang は詳しくないので TypeScript について説明してみます。

以下のコードはそのまま TypeScript のコンパイルが通ります。（JSのコードと全く同じです）
もちろん**`tsconfig.json`は`noImplicitAny: true`**です。[^3]

```typescript
const triangle = {
  base: 5,
  height: 4,
  area: function() {
    return this.base * this.height / 2;
  }
};

const circle = {
  radius: 3,
  area: function() {
    return this.radius * this.radius * Math.PI;
  }
};

const rectangle = {
  width: 4,
  height: 4,
  area: function() {
    return this.width * this.height;
  }
};

for (const shape of [triangle, circle, rectangle]) {
  console.log(shape.area());
}
```

- `any`型を許容していないのに`for (const shape of [triangle, circle, rectangle])`がエラーにならないけど`shape`の型はどうなっているの？
- `shape.area()`が確実に呼び出せるのをどうやって保証してるの？

といった疑問が出てきますが、答えは画像の通りです。

![ts-union.PNG](https://qiita-image-store.s3.amazonaws.com/0/195024/b7a5272a-3e3d-1104-b561-45c03375df3e.png)

**`shape`は三角、円、四角の共用体型**になっています。
TypeScript コンパイラは**三角、円、四角が`area: () => number`を持つことを推論**します。

ためしに、`circle`から`area`をコメントアウトしてみます。

```ts
const circle = {
  radius: 3
  // area: function() {
  //   return this.radius * this.radius * Math.PI;
  // }
};
```

すると、エラーが出ています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/efbad334-29f0-28c6-4c14-8f9ff38b8456.png)

```
プロパティ 'area' は型 '{ base: number; height: number; area: () => number; } | { radius: number; } | { width: number; h...' に存在しません。
プロパティ 'area' は型 '{ radius: number; }' に存在しません。
```

円に`area`が定義されておらず、円で`shape.area`すると実行時エラーが発生することを教えてくれます。
残念ながら、素の JavaScript だと、エディタ上でエラーは出ずに、実行時に例外をスローします。

JavaScriptと同じように手軽にダックタイピング可能で、
エラーになるものはエディタ上でリアルタイムに教えてくれる TypeScript 凄く賢いです...:blush:

## **まとめ**

- **共通の振る舞いを持ちながら、実装の違いによって現れる`if`や`switch`はポリモーフィズムを見逃している証**
- **ポリモーフィズムは動的型付けではダックタイピング、静的型付けでは抽象クラスの継承やインターフェースの実装をすることで実現できる**

___

2018/07 追記
間違って記事を削除してしまい再投稿しました。:scream::scream::scream:
ストックしていただいた方、申し訳ありません:bow::bangbang:

[^1]: 正確には`area`プロパティの持つ関数式です。
[^2]: `interface`の定義して実装することも可能です。
[^3]: 「暗黙の`ANY`を許容しない」にしないと「TS は動的(`any`)型付けとしても扱えるからコンパイルが通る」という理由になるので必要です。