TypeScriptのクラスについて②

https://qiita.com/toraguitar/private/b9cba23ed8e0a52fe7a4

上の記事の続き。
## 継承
宣言されたクラスを基に、新たなクラスを宣言できる。
クラスの宣言時に`extends`を用い、基にしたいクラスを記述する。
この時元となったクラスを「ベースクラス」、新たに宣言したクラスを「サブクラス」という。

サブクラスはベースクラスを継承しており、
特に記述をしなければベースクラスのプロパティとコンストラクターの処理が適用される。




``` ts
// 元となるクラス
class Person {
    constructor(
        private name: string,
        public age: number,
        country: string,
        private hobbies: string[] = []
    ) {

    }
    .....
};

// サブクラス
class JapanesePerson extends Person {
    // 何も書かなければ、ベースクラスのconstructorの内容が実行される
}

const man = new JapanesePerson('John', 30, 'JAPAN');
```

コンストラクターを新たに宣言する場合、`super`を用いる。
サブクラスのコンストラクターの情報を、ベースクラスのコンストラクターに渡す。
`super`を使用する場合、必ずコンストラクターの最初で使用する。

``` ts
class Person {
    constructor(
        private name: string,
        public age: number,
        private country: string,
        private hobbies: string[] = [];
    ) {

    }
    showData() {
        console.log(this.name);
        console.log(this.age);
        console.log(this.country);
        console.log(this.hobbies);
    }
};

// 日本人限定なので、countryに常に'JAPAN'を渡したい場合
class JapanesePerson extends Person {
    todofuken: string;

    constructor(name: string, age: number, todofuken: string) {
        // 受け取った引数と固定したい値をベースクラスの引数に渡す
        super(name, age, 'JAPAN');
        // 新たに定義した引数は初期化する
        this.todofuken = todofuken;
    }
}

// サブクラスのnew時はベースクラスの引数＋サブクラスで定義した引数
// 固定した引数の値(今回であれば`country`プロパティ)は渡す必要がない
const japanese = new JapanesePerson('Masao', 16, 'Hokkaido');
```

## オーバーライド
オーバーライドとは、ベースクラスに存在するプロパティやメソッドの内容を上書きすることを指す。
サブクラスはベースクラスのプロパティやメソッドをそのまま呼び出すことができるが、名前をそのままにしながらサブクラス用に内容を書き換えることができる。
``` ts
class Person {
    constructor(
    ....
    ) {

    }
    // ベースクラスではただデータを出力するだけ
    showData() {
        console.log(this.name);
        console.log(this.age);
        console.log(this.country);
        console.log(this.hobbies);
    }
};
class JapanesePerson extends Person {
    ....
    showData() {
        // 太郎くんが追加された時だけ出力しないようにする
        if (name === 'Taro') {
            return;
        }
        console.log(this.name);
        console.log(this.age);
        console.log(this.country);
        console.log(this.hobbies);
    }
}
```


## protected
アクセス修飾子。サブクラスからのアクセスを許可したい場合に使用する。
private同様、クラス外からのアクセスは制限される。

``` ts
class Person {
    constructor(
    ....
        private hobbies: string[] = []; // privateの状態
    ) {

    }
    ....
};
class JapanesePerson extends Person {
    ....
    // 趣味を追加する関数
    addHobbies(hobby: string) {
        // hobbiesがprivateのため、アクセスできずエラーになる
        this.hobbies.push(hobby);
    }
}
```
``` ts
class Person {
    constructor(
    ....
        protected hobbies: string[] = []; // protectedの状態
    ) {

    }
    ....
};
class JapanesePerson extends Person {
    ....
    // 趣味を追加する関数
    addHobbies(hobby: string) {
        // hobbiesがprotectedのため、アクセスできるようになる
        this.hobbies.push(hobby);
    }
}
```

## GetterとSetter 
### Getter
privateなプロパティにアクセスし、値を取得するためのメソッド。
接頭辞`get`でメソッドを定義する。
getで定義したメソッドは、必ず値を返さなければならない。

```ts
class Person {
    // 直近追加した人の趣味のデータ
    private lastHobby: string;

    // メソッドを定義する
    get recentHobby() {
        return this.lastHobby;
    }
    constructor(
        private name: string,
        public age: number,
        private country: string,
        private hobbies: string[] = [];
    ) {

    }
    addHobbies(hobby: string) {
        this.hobbies.push(hobby);
        // 趣味を追加する際、lastHobbyにもhobbyの値を追加する
        this.lastHobby = hobby;
    }
};
```
getで定義したメソッドは、ドットで呼び出すことができる。
通常のメソッドと異なり、`()`は不要。

```ts
const man = new Person('John', 30, 'JAPAN');

man.addHobbies('Sports');

console.log(man.hobbies); // -> ['Sports']
console.log(man.resentHobby); // -> 'Sports'

man.addHobbies('Reading');

console.log(man.hobbies); // -> ['Sports', 'Reading']
console.log(man.resentHobby); // -> 'Reading'
```

エラーメッセージを設定するような、複雑な実装もできる。
```ts
    // メソッドを定義する
    get recentHobby() {
        // lastHobbyがある場合のみ、値を返す
        if (this.lastHobby) {
            return this.lastHobby;
        }
        throw new Error('この人に趣味はありません');
    }

```

### Setter
privateのプロパティにアクセスし、値を追加する場合に使用する。
接頭辞`set`で定義する。
setで定義したメソッドは、必ず引数を渡さなければならない。

```ts
class Person {
    // 直近追加した人の趣味のデータ
    private lastHobby: string;

    // メソッドを定義する
    set recentHobby(value: string) {
        // addHobbiesを呼び出し、valueを引数として渡す。
        this.addHobbies(value);
    }
    constructor(
        private name: string,
        public age: number,
        private country: string,
        private hobbies: string[] = [];
    ) {

    }
    addHobbies(hobby: string) {
        this.hobbies.push(hobby);
        this.lastHobby = hobby;
    }
};
```

setで定義したメソッドは、ドットで呼び出すことができる。
通常のメソッドと異なり、`()`は不要。

```ts
const man = new Person('John', 30, 'JAPAN');

man.recentHobby = 'Sports'; // ドットでアクセスし、イコールで引数を渡す

console.log(man.hobbies); // -> ['Sports']
```

エラーメッセージを設定するような、複雑な実装もできる。
```ts
    // メソッドを定義する
    set recentHobby(value) {
        // valueが空文字の場合、エラーを返す
        if (this.lastHobby) {
            throw new Error('無趣味ってことですか？');
        }
        this.addHobbies(value);
    }
```

## static
クラスに直接アクセスし、静的なプロパティやメソッドを定義するための修飾子。
staticで定義したものは、newでインスタンス化しなくても呼び出すことができる。

::: note
**具体例**
すでにJavaScriptに存在するものだと、`Math`が該当します。
これは`new Math()`のようにせずとも利用することができます。
:::

```ts
class Person {
    // いつのデータかを静的に宣言
    static year = 2024;
    
    // 人を作る
    static createPerson(name: string) {
        return { name: name };
    }

    constructor(
        private name: string,
        public age: number,
        private country: string,
        private hobbies: string[] = [];
    ) {

    }
};

const person = Person.createPerson('Yuta');
console.log(Department.year, person); // -> 2024, { name: 'Yuta' }
```
クラス内部から`this`でアクセスしたり、
インスタンス化したクラスからはアクセスできない。

```ts
class Person {
    static year = 2024;

    constructor(
        private name: string,
        public age: number,
        private country: string,
        private hobbies: string[] = [];
    ) {
        console.log(this.year); // -> アクセスできずエラーになる
        console.log(Person.year); // -> 2024
    }

    // staticで定義したメソッドからはアクセスできる
    static showYear() {
        console.log(this.year);
    }
}

const man = new Person('John', 30, 'JAPAN');
console.log(man.year); // -> アクセスできずエラーになる
```

## abstract(抽象)
継承専用であることを明示するための修飾子。
クラスの定義や、メソッドの前に記述する。

クラスを抽象化すると、ベースクラス自体をnewでインスタンス化できなくなる。
メソッドを抽象化すると、継承先でメソッドを上書きすることを強制する。

``` ts
// Musicクラスを抽象化する
abstract class Music {
    name: string;
    category: string;

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

    // 関数名のみ定義し、継承先で上書きすることを強制する
    abstract describe(): void;
}

// MusicをベースにPopMusicをサブクラスとして定義する
class PopMusic extends Music {
    constructor(n: string) {
        super(n, 'POP');
    }

    // サブクラスの定義時に上書きしないとエラーになる
    describe() {
        console.log(this.name);
    }
}

// サブクラスのインスタンス化は問題なくできる
const pop = new PopMusic('YOASOBI'); // -> OK

// Musicのクラス自体をインスタンス化することはできない
const music = new Music('YOASOBI', 'POP'); // -> NG
```

## シングルトンクラス

インスタンス化を最初の一回に限定したい場合、クラスをシングルトンクラスにする。
コンストラクターをprivate化し、クラスの内部でインスタンス化を行う。

```ts
class Music {
    name: string;
    category: string;
    // インスタンス化したオブジェクトを格納するプロパティを用意する
    private static instance: Music;

    // コンストラクターをprivateにする
    private constructor(n: string, c: string) {
        this.name = n;
        this.category = c;
    }

    //インスタンス化を行うメソッドをstaticで定義する
    static getInstance() {
        // すでにインスタンス化されたオブジェクトがあればそれを返す
        if (this.instance) {
            return this.instance;
        }
        this.instance = new Music('YOASOBI', 'POP');
        return this.instance;
    }
}

const music = Music.getInstance(); // -> OK
const music = new Music('YOASOBI', 'POP');  // -> NG
```