クラスとインタフェース TypeScript & Angular 勉強会 #5

## 演習問題回答

---

## Typescriptのクラスの特徴
- C# から受け継いだ、Java/Delphiの流れを受け継いだクラスベースのオブジェクト指向
    - 継承、インタフェース、アノテーション
- Javascript から展開された、プロトタイプベースのオブジェクト指向
    - ミックスイン

の双方の影響を受けている

---

## クラス記法

```ts
class Position { //クラスの宣言
  constructor (
    private x: number, //コンストラクタでの引数宣言と、クラスのメンバ宣言を同時に行うこともできる
    private y: number
  )

  private member: number //メンバ変数の宣言
  
  protected member_protected: string //protected
  public member_public: Object //public, デフォルトのアクセス指定子
  private readonly member_ro: boolean //readonly

  public method(arg: number): string { //メンバー関数
     return arg.toString()
  }

}

let pos = new Position(100, 200) //x:100, y:200 のPositionをnewする
```

---

## 継承と抽象クラス
`extends` キーワードにより継承によるクラスの拡張を行うことができます。

```ts
//麻雀牌
class Tiles {
}

//数牌
class Suites extends Tiles {
  protected num: number = 1
}

//萬子
class Characters extends Suites {
  public printTile(): void {
     console.log("")
  }
}

let character = new Characters()
```

本来は、萬子以外の牌がインスタンス化されることはないので、 `abstract` キーワードで抽象クラスにします。

```ts
//麻雀牌
abstract class Tiles {
}

//数牌
abstract class Suites extends Tiles {
  protected num: number = 1
}

//萬子
class Characters extends Suites {
}

let suit = new Suites() // Cannot create an instance of an abstract class.(2511)
```
---

## super() による親クラスコンストラクタの呼び出し
現在の実装だと、数字が1の数牌しか定義できないので、コンストラクタを追加しましょう。

```ts
//麻雀牌
abstract class Tiles {
}

//数牌
abstract class Suites extends Tiles {
  constructor(
    protected num: number
    ) {
    super() //継承クラスでコンストラクタを定義した場合、親のコンストラクタをsuperで呼び出す必要がある
  }
}

//萬子
class Characters extends Suites {
}

let ch = new Characters(1) //明示的にコンストラクタを定義しない場合、暗黙的に親クラスのコンストラクタを利用できる
```

ついでに赤牌も定義しておきましょう。

```ts
class RedCharacters extends Characters {
  constructor(){
    super(5) //赤牌は5限定なので親クラスコンストラクタをパラメータ付きで呼び出す
  }
}
```

---
## super による親メソッド呼び出し
牌の種別を文字列で出力する関数を考えましょう。
五萬赤牌は、継承元のprintメソッドを使いたいため、superで親メソッドにアクセスしています。

```ts
//萬子
class Characters extends Suites {
  print(): string{
    return `${this.num}萬`
  }
}

//五萬赤
class RedCharacters extends Characters {
  constructor(){
    super(5) //赤牌は5限定なので親クラスコンストラクタをパラメータ付きで呼び出す
  }

  print(): string{
    return `${super.print()} 赤`
  }
}
```

---
## 戻り値の型としての this
自分自身と同じものを作成する関数 `clone` を考えます。

共通の関数なので、大本の `Tiles` に作成することにしましょう。

```ts
abstract class Tiles {
  public clone():Tiles {
    return {...this}
  }
}

//数牌
abstract class Suites extends Tiles {
  constructor(
    protected num: number
    ) {
    super()
  }
}

//萬子
class Characters extends Suites {
  print(): string{
    return `${this.num}萬`
  }
}

//五萬赤
class RedCharacters extends Characters {
  constructor(){
    super(5) //赤牌は5限定なので親クラスコンストラクタをパラメータ付きで呼び出す
  }

  print(): string{
    return `${super.print()} 赤`
  }
}

let ch = new Characters(1)
let ch2:Characters = ch.clone() //Type 'Tiles' is missing the following properties from type 'Characters': print, num(2739)
```

しかしその場合、継承先クラスでも親クラスを返してしまうため、コンパイルエラーとなります。

ここで、thisを用いた型づけをすると、継承先のクラスの型をリターンするようになるため、エラーを発生させずに実装できます。

```ts
abstract class Tiles {
  public clone():this{
    return {...this}
  }
}
```

---
## インタフェース
インタフェースは、実装を持たずに型の宣言のみを持ったクラスです。

```ts
interface LogItem {
  message: string,
  type: number,
  print: () => string
}
```

実際のところ、以下の型エイリアスとかなり似ており、いくつかの違いを除いてほぼ同一に扱うことができます。

```ts
type LogItem = {
  message: string,
  type: number,
  print: () => string
}
```

インタフェースも継承させることができます。

```ts
interface UILogItem extends LogItem {
  element: Element
}
```

---
## 型エイリアスとインタフェース
型エイリアスとインタフェースは大きく3つの違いがあります。

### 型エイリアスでしか表現できない型がある
インタフェースは、あくまでにObject型のようにアトリビュートを持った型の構造を規定するものです。
つまり、以下のような構造を持たない型については、interfaceは宣言する術を持ちません。

```ts
type numberAlias = number
type Direction = "東" | "南" | "西" | "北"
```
### インタフェースは拡張時に拡張元の型が割り当て可能かを厳密に判断する
以下のようなインタフェース宣言は不可能です。

```ts
interface LogItem {
  message: string,
  type: number,
  print: () => string
}

interface UILogItem extends LogItem { //Interface 'UILogItem' incorrectly extends interface 'LogItem'.
  print: () => Element
}
```

なぜなら、拡張元のインタフェース `LogItem` が、拡張先のインタフェース `UILogItem` に代入できなくなっているからです。

似たようなことを 型エイリアスでやってみます。

```ts
type UILogItem2 = LogItem & {print: () => Element}
```

この場合、コンパイルエラーは発生せず、うまくマージされた型を作ってくれます。

### インタフェース宣言は、マージされる
インタフェースは以下のように分割して定義しても、コンパイル時にマージされます。

```ts
interface Animal {
  name: string
}

interface Animal {
  canFly: boolean
}

let animal: Animal = {
  name: "Lion",
  canFly: false
}
```

---
## インタフェース宣言の実装
インタフェース宣言は、型エイリアス同様に型定義として利用できますが、クラスに対する制約としても定義できます。
インタフェースは `extends` ではなく、 `implements` です。

```ts
interface Animal {
  name: string
}

interface Animal {
  canFly: boolean
}

class Cat implements Animal {
  constructor(
    public name: string,
    public canFly: boolean) {
  }
}
```

また、インタフェースは複数を実装することも普通です。

```ts
interface Animal {
  name: string
  canFly: boolean
}

interface Pet {
  ownerName: string
  nickname: string
}


class Cat implements Animal, Pet {
  constructor(
    public name: string,
    public nickname: string,
    public ownerName: string,
    public canFly: boolean) {
  }
}
```

この「複数のインタフェースを持てる」という部分が、今後説明する設計パターンを活用する上での重要なポイントになります。

---
## 演習問題
麻雀牌のクラスを役牌含めて完成させてください。
継承、インタフェース、抽象クラスなど、今回説明したものは何でも用いてOKです。
（途中実装が資料にありますが、それを無視しても構いません）


---