📖

【イラスト付き】TypeScriptでのオブジェクト指向【丁寧に解説】

2024/09/04に公開

はじめに

皆さんこんにちは。
今回はTypeScriptでのオブジェクト指向プログラミングをご紹介します。

TypeScriptはJavaScriptが持つ言語仕様に加えて、クラスをより本格的に使ったオブジェクト指向を実現する機能が追加されています。JavaScript単体ではクラスの記述方法に限界があったり、他のクラスベースのオブジェクト指向言語には当然ある機能が欠けています。今回はTypeScriptでのオブジェクト指向プログラミングで使える要素についてご紹介します。

こんな人にオススメ

  • TypeScriptのクラスの書き方を知りたい
  • TypeScriptの継承やインターフェースの実装の書き方を知りたい
  • TypeScriptのポリモーフィズムの書き方を知りたい
  • TypeScriptのEnumの書き方を知りたい

初めて学習する方にも分かるように、丁寧に解説していきます。
すでに利用されている方も、是非一度目を通していただけると嬉しいです。

😋 TypeScriptのオブジェクト指向プログラミングをご紹介します♪

クラス

まずポイントをチェック

  • プロパティをクラス内に直接宣言可能
  • アクセス修飾子を利用可能(private, protected)
  • 静的メンバを宣言可能(static)

クラスはJavaScriptでも標準的に備わっている文法です。TypeScriptではよりクラスベース言語のような記述ができるようになっています。これによりカプセル化が可能になります。

プロパティをクラス内に直接宣言可能

クラス内で直接プロパティ(フィールド)を宣言することができます。

アクセス修飾子を利用可能(private, protected)

クラスのメンバにアクセス修飾子をつけることができます。これによって必要な範囲にのみ機能を公開することができます。なおアクセス修飾子を何も指定しない場合は、デフォルトでpublicになります。

privateなプロパティにアクセスするためのgetterとsetterを用意することもできます。

getterの宣言はgetキーワードを使い、setterの宣言はsetキーワードを使います。それぞれの名前は慣習としてアンダースコアを抜いたプロパティ名にします。

getterとsetterは擬似的なプロパティとして動作するので、利用時は「()」をつけません。

TypeScript/06.oop/app.ts(クラスのみ抜粋)
// クラス ----------------------
class Book {
    private _title: string;
    constructor(title) {
        this._title = title;
    }

    // getter
    get title(): string {
        return this._title;
    }

    // setter
    set title(title: string) {
        this._title = title;
    }
}
// Bookのインスタンス化
const book = new Book('恋空');
book.title = '君の名は'; // setter
console.log(book.title); // getter

静的メンバを宣言可能(static)

staticキーワードを使い静的メンバを宣言することもできます。静的メンバはインスタンス化しなくてもクラスから直接呼び出せます。

TypeScript/06.oop/app.ts(staticのみ抜粋)
// utilクラス ----------------------
class Util {
    static printHello() {
        console.log('Util hello');
    }
}
Util.printHello(); // staticメンバの利用

😋 アクセス修飾子やstatic修飾子が使えます♪

抽象クラスと継承

まずポイントをチェック

  • abstractで抽象クラスや抽象メソッドを宣言可能
  • extendsでクラスを継承
    • 抽象メソッドは全てオーバーライドする(しないとコンパイルエラー)
  • インスタンスは親クラス型の変数に代入可能

TypeScriptでは抽象クラスを宣言することができます。抽象クラスには抽象メソッドを持たせることができます。

抽象クラスと抽象メソッドはabstractキーワードで宣言できます。抽象メソッドは具体的な処理内容は定義しないので、引数と戻り値の型のみ定義します。

JavaScriptは標準的にクラスの継承を行うことができます。extendsキーワードを使います。

抽象クラスを継承する際は、抽象メソッドをオーバーライドします。オーバーライドは同名のメソッドを宣言することで行えます。その際に引数や戻り値の型を変えてしまうとコンパイルエラーになります。

下記の例は、抽象クラスを継承した子クラスのインスタンスを親クラス型の変数に代入しています。他のクラスベースの言語と同様に、親クラス型に宣言されていないメソッドは呼び出すことはできません。そのため、Animal型の変数からsmellメソッドを呼び出すことはできません。

TypeScript/06.oop/app.ts(抽象クラスと継承のみ抜粋)
// 抽象クラス ----------------------
abstract class Animal {
    abstract walk(): void;
}
// 継承
class Dog extends Animal {
    walk() {
        console.log('てくてく歩く');
    }
    smell() {
        console.log('クンクン嗅ぐ');
    }
}
// 親クラス型に代入
const dog: Animal = new Dog();
dog.walk();
// dog.smell(); // 子クラスのメソッドは呼び出せないのでコンパイルエラー

😋 abstractで抽象クラスの宣言、extendsで継承ができます♪

インターフェースとポリモーフィズム

まずポイントをチェック

  • インターフェースでの抽象メソッドはabstractはつけない
  • 実装クラスでは全てのメソッドをオーバーライドする
    • インターフェース型の変数にインスタンスを代入可能
  • 抽象クラスやインタフェースを活用しポリモーフィズムが可能

TypeScriptでは、インターフェースを利用することができます。オブジェクトの型を定義する際にも利用できますが、オブジェクト指向を実現する要素としても利用できます。

インターフェースには抽象メソッドを定義することができます。抽象クラスとは異なり、インターフェースの抽象メソッドはabstractをつけません。

インターフェースの実装クラスはimplementsでインターフェースを指定します。実装クラスでは全ての抽象メソッドをオーバーライドします。その際に引数や戻り値の型を変えてしまうとコンパイルエラーになります。

下記の例は、インターフェースの実装クラスのインスタンスをインターフェース型の変数に代入しています。他のクラスベースの言語と同様に、インターフェース型に宣言されていないメソッドは呼び出すことはできません。

TypeScript/06.oop/app.ts(インターフェースと実装クラスのみ抜粋)
// インターフェース ----------------------
interface Car {
    drive(): void;
}
// 実装クラス
class Bus implements Car {
    drive() {
        console.log('バスが走る');
    }
}
// インターフェース型に代入
const bus: Car = new Bus();
bus.drive();

クラスの継承やインターフェースを活用し、ポリモーフィズムを実現することもできます。これにより変数に代入されているオブジェクトによって、同じメソッド呼び出しでも実行内容が変化します。

下記の例は、Carインターフェースの実装クラスとしてBusクラスとTaxiクラスを用意しています。それぞれ同じインターフェースの実装クラスなので同じメソッドを持ちますが、オーバーライドの内容が異なります。それぞれのインスタンスをインターフェース型の配列にセットし、繰り返し処理でdriveメソッドを呼び出します。1周目と2周目ではcar変数に格納されているインスタンスが異なるため実行内容が変わります。

TypeScript/06.oop/app.ts(ポリモーフィズムのみ抜粋)
// ポリモーフィズム ----------------------
class Bus implements Car {
    drive() {
        console.log('バスが走る');
    }
}
class Taxi implements Car {
    drive() {
        console.log('タクシーが走ります');
    }
}
// インターフェース型に代入
const bus: Car = new Bus();
const taxi: Car = new Taxi();
// インターフェース型の配列にセットして繰り返し
const cars: Car[] = [bus, taxi];
for (const car of cars) {
    car.drive(); // car変数のオブジェクトによって実行内容が変化
}

😋 インターフェースや継承でポリモーフィズムが実現できます♪

Enum(列挙型)

まずポイントをチェック

  • enumキーワードで定数を列挙する
  • enumの個別の値は0からの数値が割り当てられている
  • 想定外の値が使われる可能性がないので、可読性が上がる

Enumは定数を列挙することができる仕組みです。Enumで定義された値意外が使われることがないので、安全なプログラムになります。

enumキーワードで宣言します。利用時は「Enum名.列挙定数名」の形式で利用します。

TypeScript/06.oop/app.ts(Enumのみ抜粋)
// Enum ----------------------
// 科目を列挙
enum Subjects {
    Japanese,
    English,
    Math
}
// 引数にEnumの定数を受け取る関数
const fn = (sub: Subjects) => {
    switch (sub) {
        case Subjects.Japanese:
            return '国語';
        case Subjects.English:
            return '英語';
        case Subjects.Math:
            return '数学';
    }
};
console.log(fn(Subjects.Japanese)); // 国語
console.log(Subjects.English); // 0

😋 定数を列挙でき、想定外の値が利用されなくなります♪

おわりに

皆さん、お疲れ様でした。
ここまでご覧いただき、ありがとうございました。

TypeScriptでのオブジェクト指向について確認をしていただきました。
TypeScriptではJavaScriptよりもクラスベースのオブジェクト指向に近い記述をすることができます。
このようにオブジェクト指向と関数型プログラミングが同居する言語仕様になっています。

😋 これからもプログラミング学習頑張りましょう♪

参考リンク集(MDN Web Docs のリンク)
参考リンク集(サンプルコード)

Discussion