抽象クラス

抽象クラス(abstract class)を使うと、あるプロパティ値の定義を必ず行ってほしいことを継承先のクラスへ伝えることができます。

抽象クラスは、単体では使うことができません。必ずクラスで継承して使います。
インターフェース(interface)との違いは、抽象クラスは共通になりそうな部分はプロパティ値を入れたりやメソッドを実装した状態で用意できる点です。

抽象クラスを定義する

クラスはclassというワードで定義していましたが、これはabstract classというワードで定義します。また、サブクラスに実装を任せたい値やメソッドにもabstract修飾子を付けられます。

abstract class A {
  // あとで!
  abstract foo: string;
  
  // あとで!
  abstract bar(): string;
  
  baz() {
    console.log('baz');
  }
}

注意なのが、abstract修飾子は、public, protected, privateのようなアクセス修飾子やプライベートフィールド(#プレフィックスから始まるプロパティ)と一緒に使うことはできません。以下のような書き方は全部エラーになります。

abstract public foo: string;
abstract private foo: string;
abstract #foo: string

定義した抽象クラスを使うには、クラスの継承のようにextendsのあとに置くだけです。と言っても、以下のように書いただけの時AAは型エラーになります。

class AA extends A {}
// Non-abstract class 'AA' does not implement inherited 
// abstract member 'bar' from class 'A'.
//
// Non-abstract class 'AA' does not implement inherited
// abstract member 'foo' from class 'A'.

これはabstract修飾子を付けて宣言したものは、継承先で必ず実装しなければならない為です。foobarを以下のように実装すれば型エラーは解消できます。

class AA extends A {
  foo = 'foo';
  
  bar() {
    return 'bar';
  }
}

注意なのが上記のfoopublic fooとはできても、protected fooprivate fooとは宣言できません。abstractはサブクラスではpublicアクセス修飾子と同等になります。

インスタンスを作成した時に、bazは抽象クラスで宣言したものがそのまま使うことができます。もちろんAAで実装したfoobarも使えます。

const aa = new AA();
aa.baz();
// 'baz'

aa.foo;
aa.bar();

抽象クラスの継承

抽象クラスは以下のように継承することができます。

abstract class AA extends A {
  foo = 'foo';
  
  abstract qux: string;
}

fooを抽象プロパティではなくしたり、新たな抽象プロパティ・メソッドを宣言することができます。

これを継承して作るクラスはbarquxの実装が必須になります。

class AAA extends AA {
  bar() {
    return 'bar';
  }
  
  qux = 'qux';
}