抽象クラスは abstract を付けた基底クラスです。new で作ることができず、本体のない abstract メソッドを宣言でき、すべてのサブクラスがそれを実装しなければなりません。
Shape は describe() をすべてのサブクラスと共有し、それぞれに area() を用意させます。Shape[] 型にはサブクラスを混ぜて入れられます。
抽象クラスはインスタンス化できない
抽象クラスに new を使うとコンパイルエラーになります。できあがるオブジェクトが実装のないメソッドを持ってしまうからです:
index.ts(6,11): error TS2511: Cannot create an instance of an abstract class.
これはコンパイル時のルールです。abstract は出力から消えるので、出力された Shape は普通のJavaScriptのクラスで、型のないJavaScriptのコードからは new Shape() を呼べてしまいます。ライブラリで実行時のガードが必要なら、コンストラクターで new.target をチェックできます:
抽象メソッドと抽象プロパティ
サブクラスはすべての抽象メンバーを実装するか、自分も抽象クラスにしなければなりません。1つ足りないとエラー TS2515(Non-abstract class 'Square' does not implement inherited abstract member area from class 'Shape' のような文面)、複数足りないときはそれらをすべて並べた TS2654 になります。
フィールド、getter、メソッドのどれも抽象にでき、protected にもできます:
抽象メソッドには本体がありません。本体を書くとエラー TS1245(Method 'x' cannot have an implementation because it is marked abstract)です。抽象クラスが抽象メンバーを持つ必要はまったくありません。abstract だけでも、直接のインスタンス化を防げます。
テンプレートメソッドパターン
抽象クラスを書くいちばんよくある理由です。基底クラスが手順の順序を決め、サブクラスがそれぞれ異なる手順を埋めます。
この例のように抽象クラスはジェネリックにでき、サブクラスごとにレコードの型を選べます。
抽象コンストラクターの型
抽象クラスの typeof Shape は呼び出してはいけないコンストラクターなので、typeof Shape 型の引数では new c() ができません。関数が抽象クラスを受け付ける必要がある場合(登録したり instanceof でチェックしたりする場合)は、抽象コンストラクトシグネチャで型を付けます:
abstract new は抽象クラスと具象クラスの両方を受け付けます。普通の new は具象クラスしか受け付けません。
抽象クラスとインターフェースの比較
| 抽象クラス | インターフェース | |
|---|---|---|
| コードを持つか | はい: メソッド、フィールドの初期値、コンストラクター | いいえ、型だけ |
| 実行時に存在するか | はい | いいえ、消去される |
instanceof | 使える | 使えない |
| クラスが使える数 | 1つ(extends) | いくつでも(implements) |
| アクセス修飾子 | private、protected、public | public メンバーだけ |
| 普通のオブジェクトの型として | private/protected メンバーがない場合だけ。その場合も instanceof は失敗する | 使える。その形を持つどんな値でも |
目安として、形を表すだけならインターフェース(いちばんよくあるケースで、オブジェクトリテラルにも使えます)、サブクラスが実際の実装を共有し、足りない部分を埋めることをコンパイラーに強制させたいなら抽象クラスを使います。この2つはうまく組み合わせられます。公開の約束にはインターフェースを、それを実装する便利な基底として抽象クラスを使います。もう1つのよくある比較については interface と type の違い を参照してください。
よくある間違い
- インターフェースで十分なところに抽象クラスを使う。 基底クラスにコードがないなら、それは実行時の重さと単一継承の制限が付いたインターフェースです。
static abstractを試す。 許可されていません。static メンバーは抽象にできません。- 抽象クラスへの
newで実行時エラーが出ると期待する。new.targetのチェックを書かない限り、エラーは出ません。 - 基底クラスのコンストラクターから抽象メソッドを呼ぶ。 サブクラスのフィールドが初期化される前に実行されるので、実装からはそれらが
undefinedに見えます。
よくある質問
TypeScriptの抽象クラスとは何ですか?
abstract を付けたクラスで、new で作ることができず、実装のない abstract メンバーを宣言できます。サブクラスはすべての抽象メンバーを実装しなければなりません。コードの共有と形の要求を同時に行う方法です。
TypeScriptの抽象クラスとインターフェースの違いは何ですか?
抽象クラスは実際のコード(実装済みのメソッド、初期値のあるフィールド、コンストラクター)を持てて、実行時に存在するので instanceof が使えます。クラスが継承できるのは1つだけです。インターフェースはコードを持たず、コンパイル時に消去され、クラスはいくつでも実装できます。振る舞いを共有するなら抽象クラス、形を表すならインターフェースを使います。
TypeScriptの抽象クラスはコンストラクターを持てますか?
持てます。サブクラスが super(...) を呼んだときに実行されます。それでも抽象クラスそのものに new を使うことはできません(TS2511: Cannot create an instance of an abstract class)。
TypeScriptで static メソッドを abstract にできますか?
できません。abstract はインスタンスメンバーにしか使えず、static abstract はエラー TS1243('static' modifier cannot be used with 'abstract' modifier)です。static の形を要求するには、static メンバーを含むインターフェースでコンストラクターを型付けし、クラスをそれと照らし合わせてチェックします。
抽象クラスは実行時に強制されますか?
されません。abstract は消去されるので、コンパイルされたクラスは普通のJavaScriptで、素のJavaScriptのコードからは new できてしまいます。それが問題なら、コンストラクターで new.target をチェックして例外を投げます。