【TypeScript】クラス継承を10選で完璧に理解!

TypeScriptでのクラス継承を図解したイメージTypeScript
この記事は約30分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Webプログラミングの世界では、一つのプログラミング言語でさまざまな機能や設計パターンを実装することが求められます。

TypeScriptはその中でも人気のある言語として多くのプロジェクトで採用されています。

TypeScriptには、クラス継承という強力な機能が含まれており、この機能を活用することで、より柔軟かつ効率的なコードの設計と実装が可能となります。

この記事では、TypeScriptのクラス継承の仕組みから使い方、応用、注意点、カスタマイズ方法までを、10の詳細なサンプルコードと共に徹底的に解説していきます。

初心者の方でも安心して取り組むことができる内容となっておりますので、最後までご一緒に学んでいきましょう。

それでは、まずは「TypeScriptのクラス継承とは」から始めていきます。

●TypeScriptのクラス継承とは

クラス継承は、オブジェクト指向プログラミングの基本的な概念の一つであり、既存のクラス(親クラスまたはベースクラスとも呼ばれる)の属性やメソッドを新しいクラス(子クラスまたは派生クラスとも呼ばれる)に引き継ぐことを指します。

これにより、既存のコードの再利用や拡張が容易になり、開発の効率性を高めることができます。

このコンセプトは、TypeScriptでもサポートされており、JavaScriptのプロトタイプベースの継承をクラスベースの継承として表現することができます。

これにより、TypeScriptではより直感的かつ簡潔な継承の記述が可能となります。

それでは、具体的なサンプルコードを見ながら、クラス継承の基本的な使い方を学んでいきましょう。

●クラス継承の基本的な使い方

継承を利用することで、共通の属性やメソッドを持つ複数のクラスを効率的に実装することができます。

○サンプルコード1:基本的なクラスの継承

このコードでは、Animalという親クラスを定義し、その後にDogという子クラスを作成して、Animalクラスのプロパティやメソッドを引き継ぐ例を紹介しています。

この例では、DogクラスはAnimalクラスの特性を受け継ぎつつ、独自のメソッドを持つことができます。

// 親クラス
class Animal {
    name: string;

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

    move() {
        console.log(`${this.name}が移動します。`);
    }
}

// 子クラス
class Dog extends Animal {
    bark() {
        console.log('ワンワン!');
    }
}

const dog = new Dog('ポチ');
dog.move(); // ポチが移動します。
dog.bark(); // ワンワン!

このコードを実行すると、dogオブジェクトを通じてAnimalクラスのmoveメソッドとDogクラスのbarkメソッドの両方を呼び出すことができます。

○サンプルコード2:継承を使用したコンストラクタの活用

TypeScriptでクラスの継承を行う際、コンストラクタを適切に活用することが極めて重要です。

コンストラクタは、インスタンスの生成時に実行される特別なメソッドで、継承されたクラス(サブクラス)での使用にはいくつかの注意点が存在します。

まず、このコードでは、動物を示すAnimalクラスを作成し、それを継承して犬の種類を示すDogクラスを定義しています。

この例では、Animalクラスのコンストラクタを活用して、動物の名前を設定し、Dogクラスでは犬の品種を設定しています。

class Animal {
    constructor(protected name: string) { }

    introduce() {
        return `私は${this.name}です。`;
    }
}

class Dog extends Animal {
    constructor(name: string, private breed: string) {
        super(name);  // 親クラスのコンストラクタを呼び出す
    }

    bark() {
        return `${this.name}は${this.breed}の犬で、ワンワンと鳴きます。`;
    }
}

const shiba = new Dog('ポチ', '柴犬');
console.log(shiba.introduce());  // introduceメソッドはAnimalクラスから継承
console.log(shiba.bark());

上記のコードを実行すると、次の出力結果が得られます。

ポチは私です。
ポチは柴犬の犬で、ワンワンと鳴きます。

サブクラスのコンストラクタで親クラスのコンストラクタを呼び出すためには、superキーワードを使用します。

この例では、Dogクラスのコンストラクタ内でsuper(name);としてAnimalクラスのコンストラクタを呼び出しています。

ここでの注意点は、サブクラスのコンストラクタ内でsuperキーワードを使用する場合、それはサブクラスのコンストラクタの中で最初に呼び出す必要があります。

もし、他のコードがsuperの前に来てしまうと、TypeScriptはコンパイルエラーを出すことを覚えておきましょう。

○サンプルコード3:継承とアクセス修飾子

TypeScriptには、クラス内のプロパティやメソッドの可視性を制御するための特有の修飾子が存在します。

ここでは、TypeScriptのアクセス修飾子と継承を組み合わせた使用方法を詳しく学びます。

このコードでは、アクセス修飾子privateprotected、およびpublicを用いて、メソッドやプロパティの可視性を表しています。

この例では、親クラスにいくつかのプロパティとメソッドを設定し、子クラスでそれらのアクセス性をテストします。

class 親クラス {
    public 公開プロパティ: string = "公開";
    protected 保護プロパティ: string = "保護";
    private 非公開プロパティ: string = "非公開";

    // コンストラクタ
    constructor() {
        console.log("親クラスのコンストラクタ");
    }

    protected 保護メソッド(): void {
        console.log("これは保護メソッドです");
    }

    public 公開メソッド(): void {
        console.log("これは公開メソッドです");
    }
}

class 子クラス extends 親クラス {
    constructor() {
        super();
        console.log(this.公開プロパティ);  // 公開
        console.log(this.保護プロパティ);  // 保護
        // console.log(this.非公開プロパティ);  // エラー: '非公開プロパティ' は private なので '子クラス' クラス内からのみアクセスできます。
    }

    子クラスのメソッド(): void {
        this.公開メソッド();  // これは公開メソッドです
        this.保護メソッド();  // これは保護メソッドです
        // this.非公開メソッド(); // エラー: 子クラスでは非公開メソッドにアクセスできません
    }
}

let インスタンス = new 子クラス();
インスタンス.子クラスのメソッド();

上記のサンプルコードでは、親クラスとして3つの異なるアクセス修飾子を持つプロパティとメソッドを定義しています。

そして、子クラスでこれらのプロパティやメソッドにアクセスしようとする際の挙動を表しています。

コードを実行すると、publicprotectedなプロパティやメソッドにはアクセスできますが、privateなプロパティやメソッドにはアクセスできないことが確認できます。

そのため、privateで定義されたプロパティやメソッドは、継承した子クラスからもアクセスできないという特性がある点に注意してください。

次に、アクセス修飾子の使用に関するいくつかの注意点とカスタマイズ方法について解説します。

アクセス修飾子は、オブジェクト指向プログラミングにおけるカプセル化の原則を実現するための強力なツールです。

それによって、クラスの内部構造や実装の詳細を隠蔽し、外部からの不正なアクセスや変更を防ぐことができます。

具体的なカスタマイズ例として、readonly修飾子を使用して、プロパティが初期化後に変更されないようにする方法があります。

これにより、一度設定された値が変更されることを防ぐことができます。

class デモクラス {
    readonly 読み取り専用プロ

パティ: string;

    constructor(初期値: string) {
        this.読み取り専用プロパティ = 初期値;
    }
}

let デモインスタンス = new デモクラス("初期値");
console.log(デモインスタンス.読み取り専用プロパティ); // 初期値
// デモインスタンス.読み取り専用プロパティ = "変更";  // エラー: '読み取り専用プロパティ' は読み取り専用です。

上記のサンプルコードでは、readonly修飾子を使用して読み取り専用のプロパティを定義しています。

このプロパティに一度値が設定されると、その後変更することはできません。

そのため、意図しない変更を防ぐための強力なツールとして利用することができます。

●クラス継承の応用例

TypeScriptのクラス継承は、単純な継承から高度なテクニックまで多岐にわたります。

ここでは、TypeScriptのクラス継承の様々な応用例を取り上げ、詳細なサンプルコードと共にその使い方を解説していきます。

○サンプルコード4:多重継承の代わりにミックスインを使用

TypeScriptは多重継承をサポートしていませんが、ミックスインというテクニックを用いて、複数のクラスの機能を組み合わせることができます。

このコードでは、2つの基底クラス、動物と飛ぶを組み合わせて、飛ぶ犬という新しいクラスを作成します。

class 動物 {
    生きている = true;
    breath(): void {
        console.log("息をしている");
    }
}

class 飛ぶ {
    空を飛ぶ(): void {
        console.log("空を飛ぶ");
    }
}

type コンストラクタ<T = {}> = new (...args: any[]) => T;

function 飛ぶミックスイン<T extends コンストラクタ>(元のクラス: T) {
    return class extends 元のクラス {
        空を飛ぶ(): void {
            console.log("空を飛ぶ");
        }
    };
}

const 飛ぶ犬 = 飛ぶミックスイン(動物);
const 空飛ぶワンちゃん = new 飛ぶ犬();
空飛ぶワンちゃん.breath();
空飛ぶワンちゃん.空を飛ぶ();

上記のサンプルコードでは、動物クラスと飛ぶクラスの2つの機能を、飛ぶミックスインという関数を介して飛ぶ犬という新しいクラスに組み合わせています。

この結果、飛ぶ犬は動物のbreathメソッドと飛ぶの空を飛ぶメソッドの両方を持つこととなります。

新しい飛ぶ犬クラスのインスタンスを生成して、breathメソッドと空を飛ぶメソッドを呼び出すと、”息をしている”と”空を飛ぶ”という結果が得られます。

○サンプルコード5:抽象クラスを使った継承

抽象クラスは、他のクラスで継承されることを前提としたクラスです。

このクラス自体はインスタンス化できませんが、サブクラスで具体的な実装を提供することで役立ちます。

このコードでは、抽象クラスとして形状クラスを定義し、そのサブクラスとして円クラスを作成します。

abstract class 形状 {
    abstract 面積を取得(): number;
}

class 円 extends 形状 {
    constructor(public 半径: number) {
        super();
    }
    面積を取得(): number {
        return Math.PI * this.半径 * this.半径;
    }
}

const myCircle = new 円(5);
console.log(`円の面積は ${myCircle.面積を取得()} です。`);

上記のサンプルコードでは、形状クラス内に面積を取得という抽象メソッドを定義しています。

このメソッドは抽象クラス内では実装されていませんが、サブクラスである円クラスでは具体的な実装が提供されています。

そのため、円クラスのインスタンスを作成し、面積を取得メソッドを呼び出すと、円の面積が計算されて結果が返されます。

円クラスのインスタンスを作成し、半径5の円の面積を取得すると、円の面積として約78.54の結果が表示されます。

○サンプルコード6:ジェネリクスを活用した継承

TypeScriptのクラス継承とジェネリクスは、相性が良く、より柔軟で再利用可能なコードを記述するための強力な手段となります。

ここでは、ジェネリクスを活用した継承を通して、その利点と使い方を詳細に解説していきます。

ジェネリクスを用いることで、型の安全性を保ちつつ、柔軟なコードを実現することができます。

特に、様々な型を扱う場面でのクラス継承において、その威力を発揮します。

class 基本コンテナ<T> {
    private data: T[] = [];
    追加(element: T): void {
        this.data.push(element);
    }
    取得(index: number): T {
        return this.data[index];
    }
}

class 文字列コンテナ extends 基本コンテナ<string> {
    文字列を結合(): string {
        return this.data.join("");
    }
}

const 文字コンテナ = new 文字列コンテナ();
文字コンテナ.追加("Type");
文字コンテナ.追加("Script");
console.log(文字コンテナ.文字列を結合());  // TypeScriptと出力されます。

このコードでは基本コンテナというジェネリクスを持つクラスを使って、任意のデータ型を保存するコンテナを実現しています。

この例では文字列型を持つ文字列コンテナという新しいクラスを継承して、文字列を結合する新しいメソッドを追加しています。

文字列コンテナのインスタンスを作成し、”Type”と”Script”という2つの文字列を追加した後、文字列を結合メソッドを呼び出すと、”TypeScript”という結果が得られます。

このように、ジェネリクスを使用することで、異なる型のデータを持つクラスを継承して、新しい機能やメソッドを追加することが簡単になります。

また、ジェネリクスを活用した継承は、型の安全性を維持しつつ、コードの再利用性を向上させることが可能です。

注意点として、ジェネリクスを使用する際は、型パラメータTの実際の型がサブクラスでどのように使用されるのかを正確に理解することが重要です。

誤った型を使用すると、ランタイムエラーの原因となる場合があります。

応用例として、複数のジェネリクスを持つクラスを定義することも可能です。

例えば、キーと値のペアを持つコンテナなど、より複雑なデータ構造を持つクラスを実装する際に活用できます。

class ペアコンテナ<K, V> {
    private items: Map<K, V> = new Map();

    追加(key: K, value: V): void {
        this.items.set(key, value);
    }

    取得(key: K): V | undefined {
        return this.items.get(key);
    }
}

const 数値と文字のコンテナ = new ペアコンテナ<number, string>();
数値と文字のコンテナ.追加(1, "one");
console.log(数値と文字のコンテナ.取得(1));  // oneと出力されます。

このコードでは、キーと値のペアを保存するペアコンテナというジェネリクスを持つクラスを実現しています。

この例では、数値をキーとし、文字列を値とするコンテナを作成しています。

数値と文字のコンテナのインスタンスを作成し、1というキーに”one”という値を追加した後、1というキーで値を取得すると、”one”という結果が得られます。

○サンプルコード7:静的メソッドと継承

TypeScriptのクラスの中には、静的メソッドという特別なメソッドが存在します。

静的メソッドは、インスタンスを生成しなくてもクラス名から直接呼び出すことができるメソッドであり、その特性上、インスタンスに依存しない処理を行う際に非常に便利です。

しかし、静的メソッドは継承の際にいくつかの注意点が必要となります。

下記のサンプルコードを通じて、その使い方と注意点を紹介していきます。

このコードでは、静的メソッドの基本的な使い方と、サブクラスでの継承時の振る舞いを表しています。

この例では、親クラスと子クラスの静的メソッドが同名の場合、子クラスのメソッドが優先されることを表しています。

class Parent {
    // 静的メソッドの定義
    static greet() {
        return "こんにちは、親クラスです!";
    }
}

class Child extends Parent {
    // 子クラスで同名の静的メソッドをオーバーライド
    static greet() {
        return "こんにちは、子クラスです!";
    }
}

console.log(Parent.greet()); // 親クラスの静的メソッドを呼び出し
console.log(Child.greet());  // 子クラスの静的メソッドを呼び出し

上記のコードを実行すると、次のような出力が得られます。

親クラスのgreetメソッドと子クラスのgreetメソッドの両方が定義されているにも関わらず、子クラスの静的メソッドが優先されることが確認できます。

具体的には、「こんにちは、親クラスです!」と「こんにちは、子クラスです!」が順に出力されるのです。

この振る舞いを理解することで、静的メソッドの継承の際の注意点や、その特性を最大限に活用する方法を学ぶことができます。

注意点としては、継承したサブクラスで親クラスの静的メソッドをオーバーライドする際、意図的に同名のメソッドを定義しない限り、親クラスの静的メソッドがそのまま引き継がれる点が挙げられます。

これは、普通のメソッドの継承と同じ動きをするため、特別に気をつける必要はありませんが、意識しておくとよいでしょう。

また、応用として、サブクラスの静的メソッドの中でsuperキーワードを利用して、親クラスの静的メソッドを呼び出すことも可能です。

これにより、親クラスの静的メソッドの振る舞いを再利用しつつ、サブクラス独自の処理を追加することができます。

○サンプルコード8:クラス継承のオーバーライドとsuperキーワード

TypeScriptでは、継承の中で特に頻繁に使用されるテクニックとして「オーバーライド」と「superキーワード」が挙げられます。

オーバーライドは子クラスが親クラスのメソッドやプロパティを新しく定義(上書き)することを指し、superキーワードは子クラスから親クラスのメソッドやコンストラクタを呼び出すためのキーワードです。

このコードでは、親クラスのメソッドをオーバーライドして、さらにそのオーバーライドされたメソッド内でsuperキーワードを使って親クラスのメソッドを呼び出す例を表しています。

この例では、動物の名前と鳴き声を取得し、それをカスタマイズして表示しています。

// 親クラス:Animalクラス
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    // 鳴き声を返すメソッド
    sound(): string {
        return '何かの鳴き声';
    }
}

// 子クラス:Dogクラス
class Dog extends Animal {
    // オーバーライド:親クラスのsoundメソッドを上書き
    sound(): string {
        // superキーワードを使って親クラスのsoundメソッドを呼び出し
        return super.sound() + '、しかし実際はワンワン!';
    }
}

const dog = new Dog('ポチ');
console.log(`${dog.name}の鳴き声は、${dog.sound()}`);

このサンプルコードを実行すると、”ポチの鳴き声は、何かの鳴き声、しかし実際はワンワン!”と表示されます。

Dogクラスでsoundメソッドをオーバーライドし、その中で親クラスのsoundメソッドをsuperキーワードを使って呼び出しているため、このような結果となります。

ここでのポイントは、継承されたメソッドやプロパティをオーバーライドすることで子クラス独自の振る舞いを追加したり変更したりできることです。

また、superキーワードを利用することで、オーバーライドされたメソッドの中からも親クラスのメソッドやプロパティにアクセスできることが確認できます。

また、オーバーライドはメソッドだけでなく、プロパティに対しても適用することができます。

プロパティのオーバーライドを利用したサンプルコードを紹介します。

class Bird extends Animal {
    // オーバーライド:親クラスのnameプロパティを上書き
    name = '鳥の名前';
}

const bird = new Bird('スズメ');
console.log(bird.name);  // 鳥の名前

このサンプルコードでは、Birdクラスで親クラスAnimalのnameプロパティをオーバーライドしています。

そのため、Birdクラスのインスタンスを生成した際に、指定した名前「スズメ」ではなく「鳥の名前」と表示されます。

オーバーライドを使って、継承元のクラスの振る舞いをカスタマイズすることで、より柔軟にコードを設計することが可能となります。

しかし、オーバーライドを過度に使用するとコードの複雑さが増すため、必要な場面で適切に利用することが肝心です。

○サンプルコード9:継承を使用したイベントハンドリング

イベントハンドリングは、ユーザーアクションやシステム発生のイベントに対して、特定の処理を実行するための技術です。

TypeScriptでクラス継承を利用して、イベントハンドリングの仕組みを実装すると、再利用性と拡張性が向上します。

今回は、親クラスで基本的なイベントハンドリングの仕組みを持ち、子クラスで具体的なイベント処理を定義する方法を解説します。

このコードでは、EventEmitterクラスを作成して、基本的なイベントの発行と購読の機能を提供します。

そして、そのクラスを継承してUserActionEmitterクラスを作り、ユーザーアクションに関する具体的なイベント処理を実装します。

この例では、EventEmitterクラスを利用して、イベントの発行と購読を行い、UserActionEmitterクラスでイベントの具体的な処理を定義しています。

// 基本的なイベントエミッタークラス
class EventEmitter {
    private listeners: { [key: string]: Function[] } = {};

    on(eventName: string, listener: Function) {
        if (!this.listeners[eventName]) {
            this.listeners[eventName] = [];
        }
        this.listeners[eventName].push(listener);
    }

    emit(eventName: string, data: any) {
        if (this.listeners[eventName]) {
            this.listeners[eventName].forEach(listener => listener(data));
        }
    }
}

// EventEmitterを継承して具体的なイベント処理を行うクラス
class UserActionEmitter extends EventEmitter {
    userClick(data: any) {
        // ユーザーがクリックした時の処理
        this.emit("click", data);
    }
}

const userAction = new UserActionEmitter();
userAction.on("click", (data) => {
    console.log(`ユーザーが${data.action}を実行しました`);
});

userAction.userClick({ action: "ボタンクリック" });

上記のコードを実行すると、「ユーザーがボタンクリックを実行しました」というメッセージが表示されます。

これは、UserActionEmitterクラスのuserClickメソッドが呼び出された際に、emitメソッドを介してイベントが発行され、それを購読している関数が実行されるためです。

注意点として、EventEmitterの内部でイベントの発行や購読を管理しているため、子クラスで新たなイベントを追加する場合も、親クラスのメソッドを利用して簡単に実装することができます。

これにより、複数の異なるイベント処理を同じ仕組みで簡単に追加することができます。

○サンプルコード10:継承を利用したデザインパターンの一例

TypeScriptのクラス継承を応用することで、さまざまなデザインパターンを実装することが可能になります。

デザインパターンは、特定の問題を解決するための設計のテンプレートやガイドとして機能します。

今回は、その中でも特に有名な「テンプレートメソッド」パターンを継承を活用して実装してみましょう。

このコードではテンプレートメソッドパターンを使って、一連のステップを持つアルゴリズムの骨格を定義し、そのいくつかのステップをサブクラスで定義することでアルゴリズムの再利用と拡張を可能にしています。

この例では、料理の手順を模倣して、基本的な手順は親クラスで定義し、具体的な料理の内容は子クラスで定義しています。

// 親クラス:料理の一般的な手順を示す
abstract class CookingProcedure {
    // テンプレートメソッド
    public cookMeal() {
        this.prepareIngredients(); // 材料を準備する
        this.cook();              // 料理する
        this.serve();             // 盛り付ける
    }

    protected abstract prepareIngredients(): void;
    protected abstract cook(): void;
    protected serve(): void {
        console.log("料理を盛り付けました!");
    }
}

// 子クラス:具体的な料理の内容を示す
class PastaCooking extends CookingProcedure {
    protected prepareIngredients(): void {
        console.log("パスタとソースの材料を準備します。");
    }

    protected cook(): void {
        console.log("パスタを茹でて、ソースと和えます。");
    }
}

上記の例において、CookingProcedureクラスが料理の一般的な手順を示す親クラスとして機能します。

このクラスの中にcookMealというメソッドがあり、これがテンプレートメソッドとなります。

具体的な料理の内容、例えば「パスタの調理」は子クラスであるPastaCookingで定義されています。

このコードを利用すると、次のように具体的な料理の手順を実行することができます。

const pastaCook = new PastaCooking();
pastaCook.cookMeal();

このコードを実行すると、次のような出力が得られます。

パスタとソースの材料を準備します。
パスタを茹でて、ソースと和えます。
料理を盛り付けました!

このように、テンプレートメソッドパターンを利用することで、特定の手順やアルゴリズムの骨格を親クラスで定義し、具体的な内容や手順の変更を子クラスで行うことができます。

TypeScriptのクラス継承の特性を活用することで、このようなデザインパターンの実装が効率的に行えるのです。

●クラス継承の注意点と対処法

TypeScriptを使用したクラス継承は、オブジェクト指向プログラミングの基本的な要素として非常に強力です。

しかし、継承を行う際にはいくつかの注意点が存在します。

ここでは、TypeScriptのクラス継承でよく遭遇する問題点と、それに対する対処方法を詳細に説明します。

○継承の際のアクセス修飾子の取り扱い

このコードでは、アクセス修飾子privateが子クラスからアクセスできないことを表しています。

この例では、親クラスのプライベート変数を子クラスから直接アクセスしようとしてエラーが生じています。

class Parent {
    private parentValue: number = 10;
}

class Child extends Parent {
    constructor() {
        super();
        // 以下の行でエラーが発生
        console.log(this.parentValue); // Error: 'parentValue' is private and only accessible within class 'Parent'.
    }
}

対処方法として、protected修飾子を使用することで、子クラスからもアクセスが可能になります。

class Parent {
    protected parentValue: number = 10;
}

class Child extends Parent {
    constructor() {
        super();
        console.log(this.parentValue); // 10
    }
}

この変更を施すことで、ChildクラスはparentValueにアクセスできるようになります。

○メソッドのオーバーライドに関する注意

次に、メソッドのオーバーライドについて考えます。

TypeScriptでは、子クラスで親クラスのメソッドをオーバーライドする際、戻り値の型も考慮する必要があります。

このコードでは、親クラスのメソッドと異なる戻り値の型を持つメソッドを子クラスでオーバーライドしています。

この例では、ParentクラスのgetValueメソッドはnumber型を返す一方、Childクラスの同じメソッドはstring型を返すためエラーが生じます。

class Parent {
    getValue(): number {
        return 10;
    }
}

class Child extends Parent {
    // 以下のメソッドでエラーが発生
    getValue(): string {
        return "child value";
    }
}

対処法としては、オーバーライドするメソッドの戻り値の型を一致させることが推奨されます。

○コンストラクタのオーバーライドとsuperの利用

継承を行う際、子クラスでコンストラクタをオーバーライドする場合、super()を必ず呼び出す必要があります。

これは、親クラスのコンストラクタを適切に初期化するための要件です。

このコードでは、子クラスのコンストラクタでsuper()を呼び出していないため、エラーが発生しています。

この例では、Childクラスのコンストラクタで親クラスのコンストラクタを呼び出すためにsuper()を使用していません。

class Parent {
    constructor() {
        console.log("Parent constructor");
    }
}

class Child extends Parent {
    constructor() {
        // 以下の行でエラーが発生
        console.log("Child constructor"); // Error: 'super' must be called before accessing 'this' in the constructor of a derived class.
    }
}

この問題を解決するためには、子クラスのコンストラクタ内でsuper()を最初に呼び出すことで、親クラスのコンストラクタが適切に初期化されます。

class Parent {
    constructor() {
        console.log("Parent constructor");
    }
}

class Child extends Parent {
    constructor() {
        super();
        console.log("Child constructor");
    }
}

このようにして、子クラスのコンストラクタでsuper()を呼び出すことで、問題を解消できます。

●継承のカスタマイズ方法

TypeScriptでのクラス継承は非常に強力ですが、標準的な使い方だけでなく、カスタマイズの方法も多数存在します。

ここでは、クラス継承のカスタマイズ方法をいくつかのサンプルコードとともに詳細に解説します。

○継承先のクラスにメソッド追加

このコードでは、親クラスから継承した子クラスに新しいメソッドを追加して、継承をカスタマイズしています。

この例では、Carクラスを継承したElectricCarクラスに、電気車特有のchargeメソッドを追加しています。

class Car {
    drive() {
        return "車が走っています";
    }
}

class ElectricCar extends Car {
    charge() {
        return "車を充電中です";
    }
}

const tesla = new ElectricCar();
console.log(tesla.drive()); 
console.log(tesla.charge());

このコードを実行すると、まずdriveメソッドによって”車が走っています”が表示され、次にchargeメソッドによって”車を充電中です”が表示されます。

○既存のメソッドをカスタマイズ

親クラスから継承したメソッドを、子クラスでカスタマイズする方法もあります。

このコードでは、AnimalクラスのmakeSoundメソッドを、Dogクラスでカスタマイズしています。

class Animal {
    makeSound(): string {
        return "ある動物の鳴き声";
    }
}

class Dog extends Animal {
    makeSound(): string {
        return "ワンワン";
    }
}

const pet = new Dog();
console.log(pet.makeSound());

Dogクラスのインスタンスを作成し、makeSoundメソッドを呼び出すと、”ワンワン”と表示されます。

○継承したプロパティの値を変更

このコードでは、親クラスのプロパティの値を、子クラスのコンストラクタで変更して、継承の振る舞いをカスタマイズしています。

この例では、Personクラスを継承したStudentクラスで、roleプロパティの値を変更しています。

class Person {
    role: string = "一般人";
}

class Student extends Person {
    constructor() {
        super();
        this.role = "学生";
    }
}

const yamada = new Student();
console.log(yamada.role);

Studentクラスのインスタンスを作成し、roleプロパティを参照すると、”学生”と表示されます。

●まとめ

TypeScriptを利用することで、オブジェクト指向の強力な特性を取り入れることが可能となり、クラス継承はその中でも特に重要な役割を果たしています。

この記事を通じて、初心者の方でもTypeScriptのクラス継承の基本から応用、そしてカスタマイズ方法までを深く理解することができたと思います。

プログラムの設計や実装において、常にベストプラクティスや新しい技術の動向に目を向け、継続的に学びを深めることが、より高品質なコードを書くための鍵となるでしょう。

TypeScriptのクラス継承をはじめとした多くの知識や技術を、これからの開発に活かしてください。