【TypeScript】TypeScript のインターフェース

#インターフェースとは
* TypeScriptのインターフェースは、オブジェクトの構造を宣言するため機能です
* インターフェースでは、プロパティと型のみ宣言します
* インターフェースはJavaScriptにない、TypeScritptの機能です

#インターフェースの使い方
* implements で、指定したインターフェースを実装します。実装したクラスを実装クラスと言います
* インターフェース実装クラスでは、インターフェースに宣言されているプロパティ・メソッドが、必ず実装されている必要があります
* インターフェース実装クラスに、インターフェースで定義されている以外のプロパティ、メソッドも追加可能です
* インターフェースは、変数の型として使う事ができます

#インターフェースのメリット
* 実装クラスでは、インターフェースのメソッドの実装を強制できます
    * 実装クラスは、インターフェースに従って実装されていることを担保できます
    * 実装クラス自体の構造を知らなくても、メソッドが存在することを明示的にできます
* 以下の具体例で示します

```ts
//インタフェース
interface Greetable {
  name: string;   //プロパティの型のみ宣言
  greet(phrase: string): void;  //メソッドの構造のみ宣言
}


//実装クラス (複数のインターフェースも実装可能)
class Person implements Greetable { 
   //インターフェースの定義以外のプロパティ、メソッドも追加可能
  name: string;
  age = 3;

  constructor(n: string) {
    this.name = n;
  }
  
  //インタフェースのメソッドを実装
  greet(phrase: string) { 
    console.log(phrase + ' ' + this.name);
  }
}


let villagerA : Greetable; //インターフェースを変数の型として使える
let heroA : Greetable;  

villagerA = new Person('村人A');  //PersonクラスのオブジェクトはGreetableを実装している
heroA = new Person('勇者A');   //同じ構造のオブジェクトを生成できる

villagerA.greet('Hello I am'); //greetメソッドを持つことを担保されている
heroA.greet('May the Force be with you, '); 

// villagerAオブジェクトの具体的な構造を気にせず、Greetableインターフェイスの greetメソッドが使える
// villagerAオブジェクトがPerson型のクラスなのか、他の型のクラスなのか、気にしなくてOKです

```


## インターフェース と type の違い
* インターフェース：オブジェクトの構造を記述することのみできます。複数なクラスで同じ機能を実装したいときに使う
* type：構造や、union型など様々な型を定義できます。用途が広い事は、メリットにもデメリットにもなります

## インターフェース と abstructクラス の違い
* abstructクラス：抽象メソッドも、具体メソッドも含めることができます
* インターフェース：メソッドを宣言するのみで、内部で実装できません 

# インターフェースの修飾子
* インターフェースのプロパティを readonly に指定してプロパティを初期化の時に一度だけ設定できるようにします
* この場合、実装クラスでも、readonly のプロパティになります

```ts

//インターフェース
interface Greetable {
  readonly name: string;
  greet(phrase: string): void;
}

 //実装クラス
class Person implements Greetable { 
  name: string;  //interfaceでreadonlyに指定してるのでreadonlyになる
  age = 3;

  constructor(n: string) {
    this.name = n;
  }

  greet(phrase: string) { 
    console.log(phrase + ' ' + this.name);
  }
}
```


#インターフェースの継承
* インターフェースでも継承が可能です
* インターフェースを細分化して定義することで、実装クラスに実装を強制する範囲を、クラスごとに変更することができます
* インターフェースは複数のインターフェースを継承可能です
    * cf: クラスは、単一のクラスのみ継承可能
* 実装クラスで、複数のインターフェースを指定して実装する事も可能です

```ts
// インターフェースを継承可能
interface Greetable extends Named , AnotherInterface

// 複数のインターフェースを指定して実装する事も可能
class Person implements Greetable , Named { ... }
```

```ts

interface Named {
  readonly name: string;
}

//Greetable とNamed  両方合わせたインターフェースを作成
interface Greetable extends Named {  //実装クラスに、Named のプロパティも持つことを強制できる
  greet(phrase: string): void;
}


//実装クラス
class Person implements Greetable {
  name: string;
  age = 3;

  constructor(n: string) {
    this.name = n;
  }

  greet(phrase: string) {
    console.log(phrase + ' ' + this.name);
  }
}
```


# 関数の構造を定義する
* インターフェースで関数の構造を定義する事ができます

###インターフェースでオブジェクトの構造を定義する場合

```ts
interface 関数名 {
  ( 引数 : 引数の型 ):戻り値の型; 
}
```

```ts
interface AddFn {
  (a: number, b: number): number; 
}

let add: AddFn;

add = (n1: number, n2: number) => {
  return n1 + n2;
};

```
### custom型で関数を定義する場合
* custom型で定義する方法と同じです

```ts
// custom型
type 関数名 = ( 引数: 引数の型 ) =>戻り値の型 ;
```

```ts
type AddFn = (a: number, b: number) => number;

let add: AddFn;
add = (n1: number, n2: number) => {
  return n1 + n2;
}
```


# optionalなプロパティ・メソッドを定義する
* 実装クラスに、プロパティが存在するかどうかを強制しない事ができます
    * 実装クラスに、インターフェースのプロパティが有ってもなくてもいいよ〜という事です
* インターフェースでプロパティに`?`をつけます
    * ex: `outputName?:string`
* インターフェースでプロパティに`?`をつけ、実装クラス内でも、`?`で任意に設定する事が可能です
* コンストラクタに`?`を設定するか、デフォルト値を設定する事で、インスタンス化する際に引数があってもなくても良いように設定できます
    * ex: `constructor(n?: string) `
    * ex: `constructor(n: string = '') `
* メソッドも`?`をつけて、optionalにすることができます
    * `greet?() : void;`


```ts
interface Named {
  readonly name?: string;
  outputName?: string; //outputNameプロパティは実装クラスに必要ではない
}

interface Greetable extends Named {
  greet(phrase: string): void;
}


//実装クラス
class Person implements Greetable {
  //実装クラス内で、任意に設定可能
  name?: string;
  age = 30;

  constructor(n?: string) { //もしnが存在したら、プロパティに値を設定する
    if (n) {
      this.name = n;  
    }
  }

  greet(phrase: string) {
    if (this.name) { //nameがあれば
      console.log(phrase + ' ' + this.name);
    } else {
      console.log('Hi!');
    }
  }
}

let villager1: Greetable;
villager1 = new Person(); //引数がなくてもOK
villager1.greet('Hello I am'); //Hi!
```