TypeScript 継承

継承はオブジェクト指向プログラミングの概念であり、サブクラスが親クラスのプロパティとメソッドを継承することを可能にします。

TypeScript 継承の実装

TypeScriptでは、継承はextendsキーワードで実装されます。あるクラスが他のクラスを継承すると、親クラスのすべてのプロパティとメソッドを受け継ぐことになります。

class Animal {
    name: string;

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

class Dog extends Animal {
    breed: string;

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

    makeSound(): void {
        console.log("Woof!");
    }
}

let myDog = new Dog("Fido", "Golden Retriever");
console.log(myDog.name); // Fido
console.log(myDog.breed); // Golden Retriever
myDog.makeSound(); // Woof!

この例では、Dogクラスはextendsキーワードを使用してAnimalクラスを継承しています。これはDogクラスがAnimalクラスのすべてのメンバを含むことができることを意味します。

Dogクラスのコンストラクタは、キーワードsuperを使用することで、自身のメンバだけでなく、親クラスのプロパティも初期化します。superは、親コンストラクタとその値を呼び出すために使用されます。

TypeScript メソッドのオーバライド

オブジェクト指向プログラミングにおいて、メソッドのオーバーライドとは、親クラスですでに定義されているメソッドをサブクラスが別の実装で提供することを可能にする機能です。メソッドをオーバーライドすると、サブクラスは親クラスの実装を自身の実装に置き換えます。

TypeScriptでは、親クラスのメソッドと同じ名前のメソッドをサブクラスに定義することで、メソッドのオーバーライドを実現します。サブクラスのメソッドは別の実装を持つこともできるし、superキーワードを使って親クラスのメソッドを呼び出し、親メソッドの振る舞いを拡張することもできます。

class Animal {
    makeSound() {
        console.log("Making a sound");
    }
}

class Dog extends Animal {
    makeSound() {
        console.log("Woof!");
        super.makeSound();
    }
}

let myDog = new Dog();
myDog.makeSound(); // "Woof!" "Making a sound"

この例では、DogクラスがAnimalクラスを継承し、makeSoundメソッドをオーバーライドしています。

DogクラスのインスタンスでmakeSoundメソッドが呼ばれると、まずDogクラスでオーバーライドされたメソッドが呼ばれ、コンソールにWoof!という文字列が出力され、次にsuper.makeSound()文で親クラスのmakeSoundメソッドが呼ばれ、Making a soundという文字列がコンソールに出力されます。

Note: super.makeSound()を呼ばずに、単純に出力だけ変更しても機能します。

TypeScript 多重継承

多重継承では、派生クラスが別の派生クラスを継承し、さらにその派生クラスが別のクラスを継承するというように、継承の連鎖が形成されます。各クラスは親クラスのプロパティやメソッドを継承し、さらに独自のプロパティやメソッドを追加することで、クラスの階層が形成されます。

TypeScriptでは一度に複数のクラスの継承が行えない点には注意が必要です。

class Animal {
    name: string;

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

    makeSound(): void {
        console.log("Making a sound");
    }
}

class Dog extends Animal {
    breed: string;

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

    makeSound(): void {
        console.log("Woof!");
    }
}

class GermanShepherd extends Dog {
    constructor(name: string) {
        super(name, "German Shepherd");
    }

    makeSound(): void {
        console.log("Bark!");
        super.makeSound();
    }
}

// class GermanShepherd extends Dog, Animalのように一度に複数のクラスを継承することはできません。

let myDog = new GermanShepherd("Max");
console.log(myDog.name); // Max
console.log(myDog.breed); // German Shepherd
myDog.makeSound(); // Bark! Woof!

この例では、AnimalクラスはDogクラスの親クラスであり、DogクラスはGermanShepherdクラスの親クラスになります。GermanShepherdクラスは、AnimalクラスとDogクラスからそれぞれnameプロパティとmakeSoundプロパティとメソッドを継承し、独自のbreedプロパティとmakeSoundメソッドを追加しています。

GermanShepherdクラスのインスタンスを作成する場合、nameプロパティとbreedプロパティはコンストラクタで設定することができます。myDogインスタンスに対してmakeSoundメソッドを呼び出すと、GermanShepherdクラスのmakeSoundメソッドが呼び出されます。これは、GermanShepherdクラスがDogクラスから継承したmakeSoundメソッドをオーバーライドして、superキーワードを使って親クラスのmakeSoundメソッドを呼び出しているからです。

多重継承は、複雑なクラス階層を作成したり、既存のクラスをベースに構築したりする際に便利です。しかし、コードの理解や保守が困難になることもあるので、注意して使用する必要があります。

TypeScript 継承の例

具体例として、RPGゲームを継承を使って実装してみましょう。

class Character {
    protected name: string;
    protected health: number;

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

    public attack(target: Character): void {
        // ターゲットを攻撃するロジックを実装
    }

    public takeDamage(damage: number): void {
        // ダメージを受け、ヘルスが減少するロジックを実装
    }
}

class Player extends Character {
    private level: number;

    constructor(name: string, health: number, level: number) {
        super(name, health);
        this.level = level;
    }

    public gainExperience(amount: number): void {
        // 経験値を得てレベルアップするロジックを実装
    }
}

class NPC extends Character {
    private difficulty: string;

    constructor(name: string, health: number, difficulty: string) {
        super(name, health);
        this.difficulty = difficulty;
    }

    public dropLoot(): void {
        // NPCを倒した後に戦利品をドロップするロジックの実装
    }
}

class Boss extends NPC {
    private specialAbility: string;

    constructor(name: string, health: number, difficulty: string, specialAbility: string) {
        super(name, health, difficulty);
        this.specialAbility = specialAbility;
    }

    public useSpecialAbility(): void {
        // ボスの特殊能力を使用するロジックの実装
    }
}

このコード例では、すべてのRPGキャラクターの基本クラスを表すCharacterクラスがあります。PlayerクラスとNPCクラスは、Characterクラスを継承し、プレイヤーが操作するキャラクターとコンピュータのための特定のプロパティとメソッドを追加しています。最後に、BossクラスはNPCクラスを継承し、ボスだけが持つ特殊な能力を追加しています。

このコード例では、メンバ変数にprotectedとprivateというアクセス修飾子を使用しています。

protectedは、クラスとそのサブクラス内でアクセス可能です。この例では、PlayerとNPCのサブクラスが利用できるようにしたいため、Characterクラスのnameとhealthのプロパティにprotectedを使用しています。

privateは、定義されたクラス内でのみアクセス可能なメンバです。この例では、Player, NPC, Bossの各サブクラスのlevel, difficulty, specialAbilityプロパティにそれぞれprivateを使用します。これらのプロパティは、クラスの外からアクセスできないようにしたいためです。

このように多重継承を行うことで、RPGゲームにおいて、それぞれ異なる特性や振る舞いを持つ多様なキャラクターを、継承によって共通の特性や機能を持ちながら作成することができました。

この記事を書いた人

著者の画像

Jeffry Alvarado

Ex-Facebook Engineer 大学ではコンピュータサイエンスを専攻し、在学中に複数のインターンシップを経験。コンピュータサイエンスが学習できるプラットフォームRecursionを創業し、CTOとしてカリキュラム作成、ソフトウェア開発を担当。


ツイート