iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

abstractが気になっている

はじめに

iimonでフロントエンジニアをしてますさいとうです。

今回は業務で見かけたabstract について気になったので調べてみました

abstract classとはなにか

abstractは抽象クラスを作成する時に宣言する修飾子になります。

抽象クラスとは抽象メソッドを1つ以上持つクラスのことです。

特徴としては以下の点が挙げられます

1.インスタンス化できない

抽象クラス自体は直接インスタンス化できず、インスタンスを作成するには その抽象クラスを継承した具体的なサブクラスを使用する必要があります。

2.抽象メソッドを含むことができる

抽象クラスは抽象メソッドを含むことができ、これらのメソッドは具体的な実装を持たず、サブクラスで必ずオーバーライドされなければなりません。

3.具体的なメソッドやプロパティも持つことができる

抽象メソッドだけでなく、具体的な実装を持つメソッドやプロパティも定義できます。これにより、共通の動作や状態をサブクラスに提供できます。

抽象クラスを使うメリット

オーバーライドしないといけないことにより、メソッドの実装忘れやメソッド名の間違いがあった時にコンパイルエラーが起きてコーディングミスを減らせる。

抽象クラスのままだとインスタンス化できないので、必要なメソッドを必ず実装してからでないと利用できません。

そのため、必ずすべてのメソッドの動作を定義して、未確定のメソッドがない状態にしなければなりません。

このようにチーム開発で実装レベルのルールを作ることができます。

実際にabstractを使ってみる

下記のコードでabstractを適用してみます。

担当楽器を説明するメソッド(describeInstrument)を追加したいです。

そしてDescribe メソッドで使えるようにしてみたいと思います。

class BandMember {
    constructor(public name: string, public age: number) {}

    // 自己紹介する
    Describe(this: BandMember): void {
        console.log(`My name is${this.name}. I am ${this.age} years old.`);
    }
}

class Performer extends BandMember {
    constructor(name: string, age: number, public instrument: string) {
        super(name, age);
    }
}

ただサブクラスにdescribeInstrument を追加します

一見、this は継承先のクラスなので実装できそうですがご覧のとおりエラーになってしまいます

ここでabstract メソッドを作成します

そうすることで必ずBandMember を継承した先では、describeInstrument というメソッドがあるという保証になり、describeInstrument を使えるようになります

ただ単にabstract メソッドを適用すると下記のようなエラーになりますので、クラスもabstract にしてあげる必要があります

これでdescribeInstrumentBandMember クラスで使用できるようになりました。

こうすることで継承先のクラスで必ずdescribeInstrument メソッドを実装する必要があります。

abstract class BandMember {
    constructor(public name: string, public age: number) {}

    // 自己紹介する
    describe(this: BandMember): void {
        console.log(`My name is ${this.name}. I am ${this.age} years old.`);
        this.describeInstrument();
    }    
        abstract describeInstrument(): void;    
}

class Performer extends BandMember {
    constructor(name: string, age: number, public instrument: string) {
        super(name, age);
    }
    // 担当楽器について紹介する
    describeInstrument(): void {
        console.log(`I play the ${this.instrument}.`);
    }
}
const saxophonist =  new Performer('hiroshi', 19, 'saxophone');
saxophonist.describe();

結果

My name is hiroshi. I am 19 years old.
I play the saxophone.

先述のとおりabstract クラスはインスタンスを作成することはできません。

作成しようとすると、、エラーになります。

これはabstract class BandMemberthis.describeInstrumentdescribe メソッド内で 使われてますが、abstract class BandMember 内にthis.describeInstrument がないためです。

abstractの欠点

抽象クラスには具体的なメソッドの実装を含むことができます。つまり、共通の動作やデフォルトの動作を抽象クラスで提供することができます。それにより実装すべきメンバの型だけを定義し、実装の詳細を含めたくないケースではabstractは適してません。

抽象クラスに具体的な実装を含めると、抽象クラスの設計意図が曖昧になります。抽象クラスは共通のインターフェースを定義するために使用されるべきですが、具体的な実装が含まれることでその役割が混在してしまいます。

そこでinterface が使えます。

interfaceではプロパティとメソッドの型定義することができ、具体的な実装は含まれません。

もちろんクラスに継承することができます。(複数のクラスにも)

interface で先程のコードを書き直し

interface BandMember {
    name: string;
    age: number;
    describe(): void;
    describeInstrument(): void;
}

class Performer implements BandMember {
    constructor(public name: string, public age: number, public instrument: string) {}

    // 自己紹介する
    describe(): void {
        console.log(`My name is ${this.name}. I am ${this.age} years old.`);
        this.describeInstrument();
    }

    // 担当楽器について紹介する
    describeInstrument(): void {
        console.log(`I play the ${this.instrument}.`);
    }
}

const saxophonist = new Performer('hiroshi', 19, 'saxophone');
saxophonist.describe();

これは省略記法を使って書いてますが、本来は子クラスでプロパティを宣言する必要があります。

abstractとinterface

個人的に通常のクラス定義はinterface を使用して、複数のサブクラスに共通のロジックを持たせたい場合にabstract を使用していけばいいんじゃないかと思いました。

終わりに

abstractを通じて抽象クラスの概念が学ぶことができました。今個人的にオブジェクト指向を少しずつ学んでいてこちらも大切な概念のため知ることができてよかったです。

弊社ではエンジニアを募集しております。

ぜひカジュアル面談でお話ししましょう!

ご興味ありましたら、ご応募ください!

Wantedly / Green