【Day9】Mapを作り始めよう【QAC24】


# やった！オブジェクト指向プログラミングだよ！！！

`/src/lib/map.ts` を作成しましょう。

```typescript:map.ts
class GameMap {}
```

`GameMap` というクラスを作りました。
([間違っても `Map` という名前のクラスを作るんじゃあないぞ！](https://typescriptbook.jp/reference/builtin-api/map))

クラスというのは、オブジェクト指向プログラミングにおいて非常に重要な概念です。

> クラスは、オブジェクトの設計図のようなもので、オブジェクトを作るための型を定義します。(by Copilot)

クラスを使うことで、カプセル化や継承なんかの概念を使うことができます。

まぁまずは Class についてもっとわかりやすく説明するところから始めましょう。

## クラスとは？

**課題**
分かりやすく説明するために、今からあなたにはこの世界の神様になってもらいます。
さぁ！早く TypeScript で生き物を創造してください！

> えっと...猫と犬と人間のクラスを作るか...

```typescript:THE_WORLD.ts
class Cat {
  public name: string = "ねこ";
  public legs: number = 4;
  private foods: string[] = ["さかな", "ねずみ"];
  private reward: string = "またたび";

  public meow() {
    console.log("にゃー");
  }

  public sleep() {
    console.log("ぐー");
    this.energy += 1;
  }

  public walk() {
    position += 1;
  }

  public feed(food: string) {
    if (this.foods.includes(food)) {
      console.log("おいしい！");
    } else {
      console.log("おいしくない...");
    }
  }
}

class Dog {
  public name: string = "いぬ";
  public legs: number = 4;
  private foods: string[] = ["にく", "どっぐふーど"];
  private reward: string = "ほね";

  public bow() {
    console.log("わんわん");
  }

  public sleep() {
    console.log("ぐー");
    this.energy += 1;
  }

  public walk() {
    position += 1;
  }

  public feed(food: string) {
    if (this.foods.includes(food)) {
      console.log("おいしい！");
    } else {
      console.log("おいしくない...");
    }
  }
}

class Human {
  public name: string = "ひと";
  public legs: number = 2;
  private foods: string[] = ["かんぜんえいようしょく", "やさい"];
  private reward: string = "ゆうきゅう";

  public quote() {
    console.log("我々は神が想像した生き物の中で最も偉大なものである。");
  }

  public sleep() {
    console.log("我々は決して眠る事は許されない。");
  }

  public walk() {
    position += 1;
  }

  public feed(food: string) {
    if (this.foods.includes(food)) {
      console.log("大変喜ばしい事である。我は主に最大限の感謝を示す。");
    } else {
      console.log("主が提供してくれた物であるため、どのような物でも我は喜んで頂こう。");
    }
  }
}
```

※`private`や`public`については次回の記事でお話します。

> こんな感じ？

あーあ。
確かに間違ってはいません。
いや...人間の表現がなんかあれなんだけど...

これら全てのクラスは大まかに同じような構造を持っています。
それだったら、`Animal`といったクラスを作って、そこから継承すれば、それぞれのクラスの共通部分をまとめたり、それぞれのクラスにおける互換性を持たせたりもできます。

```typescript:THE_WORLD.ts
abstract class Animal {
  public abstract name: string;
  public abstract legs: number;
  protected abstract foods: string[];
  protected abstract reward: string;

  protected abstract message: string;

  public quote() {
    console.log(this.message);
  }

  public abstract sleep(): void;

  public walk() {
    if (this.legs > 0) {
      position += 1;
    } else {
      throw new Error("足がないので歩けません。");
    }
  }

  public feed(food: string) {
    if (this.foods.includes(food)) {
      console.log("おいしい！");
    } else {
      console.log("おいしくない...");
    }
  }
}
```

これで、全ての生き物が継承するべき`Animal`クラスができました。
さらにここに猫、犬、人間を追加してみましょう。

```typescript:THE_WORLD.ts
...

class Cat extends Animal {
  public name: string = "ねこ";
  public legs: number = 4;
  protected foods: string[] = ["さかな", "ねずみ"];
  protected reward: string = "またたび";

  protected message: string = "にゃー";

  public sleep() {
    console.log("ぐー");
    this.energy += 1;
  }
}

class Dog extends Animal {
  public name: string = "いぬ";
  public legs: number = 4;
  protected foods: string[] = ["にく", "どっぐふーど"];
  protected reward: string = "ほね";

  protected message: string = "わんわん";

  public sleep() {
    console.log("ぐー");
    this.energy += 1;
  }
}

class Human extends Animal {
  public name: string = "ひと";
  public legs: number = 2;
  protected foods: string[] = ["かんぜんえいようしょく", "やさい"];
  protected reward: string = "ゆうきゅう";

  protected message: string = "我々は神が想像した生き物の中で最も偉大なものである。";

  public sleep() {
    console.log("我々は決して眠る事は許されない。");
  }
}
```

このように`extends`を使って、`Animal`クラスを継承することで、それぞれのクラスに共通する部分をまとめることができます。
その際、`abstract`を使って、`Animal`クラスでは定義しなかったメソッドやプロパティについては、それぞれのクラスで実装しなければなりません。

さらに言うと、`Animal`クラスは`new Animal()`としてインスタンスを生成することができません。
これは、`Animal`クラスが抽象クラスであり、「こんな感じの設計図を作ればいいと思うよ」という先輩からのアドバイスみたいなものだからです。

ちなみにこのクラスを使うためには`new`を使ってインスタンス化する必要があります。

```typescript
const cat = new Cat();
const dog = new Dog();
```

こうすることで、それぞれのクラスのインスタンスを生成することができます。
インスタンス化とは、設計図から実際のオブジェクトを作ることです。

以上これがクラスの概要です。

## じゃあマップを作ってよ

言われなくても作ります。

まずは、さっきのもぬけの殻の`GameMap`クラスにを`abstract`にしたうえで、必要そうなものを考えていきましょう。

```typescript:map.ts
abstract class GameMap {}
```

はい、今回の記事ではこれが全てです。

# マップを呼び出す処理を書きたい

後はタイル情報の埋め込みと、キャラクター移動処理、コリジョンの設定などなどなのですが、そこらへんは別の記事になってしまいます。
でも少しづつ現実味を帯びていますよね。

余った時間で、ゲームを管理するためのクラス`Game`を作っていきましょう。
これは、1 ゲームにつき 1 つだけ存在するべきクラスとなります。

```typescript:src/lib/game.ts
class Game {}
```

この`Game`には、現在のマップと、マップ切り替えメソッドを追加したいですね。
という訳で、先ほど作った`GameMap`が型となる`map`プロパティを追加しましょう。

```typescript:src/lib/game.ts
class Game {
  public map: GameMap | null = null;

  public changeMap(map: GameMap) {
    this.map = map;
  }
}
```

しかし、おおよそこれではエラーが出るかと思います。
なぜなら、`GameMap`というものがどこにあるのか、`game.ts`がそれを知らないからです。
なので、インポートしてあげましょう。

```typescript:src/lib/game.ts
import { GameMap } from "./map";

class Game {
  public map: GameMap | null = null;

  public changeMap(map: GameMap) {
    this.map = map;
  }
}
```

これで、`Game`クラスが`GameMap`クラスを認識できるようになりま...あれ？
と、TypeScript でこのようにモジュール化したりするときに、忘れてはいけないことがあります。
それは`export`です。
指定したものを、別のファイルなどで使用できるようにするには、`export`を使ってエクスポートする必要があります。

`map.ts`の`GameMap`クラスに`export`を追加しましょう。

```typescript:src/lib/map.ts
export abstract class GameMap {}
```

これで、`Game`クラスが`GameMap`クラスを認識できるようになりました。

が、`GameMap`は抽象クラスで、インスタンス化されることはありません。
なのに`changeMap`メソッドで`GameMap`を引数に取っているのはおかしいですね。
この「`GameMap`が引数」なのを「`GameMap`の継承クラスを引数」にしましょう。

```typescript:src/lib/game.ts
import { GameMap } from "./map";

class Game {
  public map: GameMap | null = null;

  public changeMap<T extends GameMap>(map: T) {
    this.map = map;
  }
}
```

これで、`GameMap`の継承クラスを引数に取ることができるようになりました。
`extends`というのは、クラスの継承の意味合いで、実際に`GameMap`を継承した別のクラスを定義するときは`class NewGameMap extends GameMap {}`のように書きます。

で、`T`です。
ちょっと前の記事で`Promise<T>`も出てきましたね。

# ジェネリクスとは

> ジェネリクスを用いると、型の安全性とコードの共通化を両立することができます。
> [ジェネリクス (generics) | TypeScript 入門 『サバイバル TypeScript』](https://typescriptbook.jp/reference/generics)

ジェネリクスは、型を概念的に扱うような感じで...うん...

じゃあ例えば引数として受け取った値をそのまま返す関数`returnSuruze`を作ってみましょう。

```javascript
const returnSuruze = (value) => {
  return value;
};
```

この関数の処理がどんなのなのかは多分見ればわかると思います。
まぁ、今扱ってるのは TypeScript なので型を指定してあげましょう。

```typescript
const returnSuruze = (value: string): string => {
  return value;
};
```

はい、これで文字列を引数にして、文字列を返す関数になりました。
でもこれだと数字とかだとエラーが出ますね。

```typescript
const returnSuruze = (value: string | number): string | number => {
  return value;
};
```

`型1 | 型2`みたいな表記は Union Types といって、`型1`または`型2`のどちらかを受け取ることができるようになります。
つまりこれは、文字列か数字を受け取って、文字列か数字を返す関数になります。
なんかいい感じに見えますよね。

```typescript
const returnSuruze = (value: string | number): string | number => {
  return value;
};

console.log(returnSuruze(2) + 4);
```

はい、`returnSuruze`に数字を渡して、それがそのまま返ってきてその数値に 4 を足して...え？エラー？

> 演算子 '+' を型 'string | number' および 'number' に適用することはできません。ts(2365)

なぜなら、`returnSuruze`が返すのは`string`か`number`のどちらかで、それは決定しないからです。

これじゃあ不便ですよね？

え？こうすればいいじゃないかって？

```typescript
const returnSuruzeString = (value: string): string => {
  return value;
};

const returnSuruzeNumber = (value: number): number => {
  return value;
};

console.log(returnSuruzeNumber(2) + 4);
```

なるほど。
これならエラーは起きませんね。

でも、これだと型の数だけ関数を作らないといけないですよね。
オブジェクトなども含めると少なくとも無限に関数を作らなければなりません。

そこで、ジェネリクスを使います。

```typescript
const returnSuruze = <T>(value: T): T => {
  return value;
};

console.log(returnSuruze(2) + 4);
```

`<T>`というので、`T`という型を作り上げます。現在、この型は何でも受け入れます。
そして、引数`value`と返り値の両方に`T`を指定しています。
そのため、引数`value`と返り値は同じ型になります。
そのため、例で 2 という`number`型を渡しているので、返り値の型も`number`になる...というわけなのです。

先ほどは何でも受け入れると言いましたが、`T`に制約を加えることもできます。

```typescript
const returnSuruze = <T extends string | number>(value: T): T => {
  return value;
};
```

これで、`T`は`string`か`number`のどちらかになります。
それでも`value: string | number`の時とは異なり、返り値は引数`value`と同じ型であることが保証されているため、エラーは起きません。

この`T`は、自ら関数呼び出し時に指定することもできます。

```typescript
console.log(returnSuruze<string>("Hello, World!"));
```

このように、`関数名<ジェネリクス型>(引数)`という形で、いつもの関数呼び出しに少し手を加えるだけで、ジェネリクス指定も出来ます。

ちなみに、`Type`の頭文字をとって良く`T`と書かれますが、他の文字でも構いません。`U`とか`K`とか`V`とか...。1 文字じゃなくても。

# これで`GameMap`の継承型を受け付けてる...？

そうなんですが、現在のコードでは「インスタンス化された`GameMap`を継承したクラス」が引数となります。
わざわざインスタンス化してから引数に渡してやるのは面倒ですよね。
（プログラミング的思考:「インスタンス化」という共通項目があるならそれを関数に落とし込むべきである）

なので、インスタンス化していない状態の`GameMap`を引数に取るようにしましょう。

```typescript
class Game {
  public map: GameMap | null = null;

  public changeMap<T extends GameMap>(map: new () => T) {
    this.map = new map();
  }
}
```

引数が`new () => T`となっています。
これは、「インスタンス化したら`T`型になるクラス」というような意味合いです。

これで、`GameMap`のインスタンス化前継承クラスを引数に取ることができるようになりました。

この`Game`クラスは絶対外で使うので、`export`しておきましょう。

```typescript
export class Game {
  public map: GameMap | null = null;

  public changeMap<T extends GameMap>(map: new () => T) {
    this.map = new map();
  }
}
```

# まとめ

これでオブジェクト指向プログラミングの基本的な概念と、昨今流行のジェネリクスについて学べましたね。
最近の多くの言語にはジェネリクスが搭載されているので、ぜひ使いこなせるようになっておきましょうね～