TypeScript 抽象クラス
抽象クラス(Abstract Class)は、TypeScript のオブジェクト指向プログラミングにおける重要な概念です。
直接インスタンス化できないクラスであり、他のクラスに継承されるための基底クラスとしてのみ使用できます。
抽象クラスは主に、サブクラスの共通のプロパティとメソッドを定義し、サブクラスに統一された構造と動作のテンプレートを提供するために使用されます。
SVG 図:抽象クラスの継承階層
なぜ抽象クラスが必要なのか
オブジェクト指向プログラミングでは、基底クラスを定義することがよくあります。
これらの基底クラスはサブクラス共通のプロパティとメソッドを定義しますが、一部のメソッドの実装詳細はサブクラス自身が決定する必要があります。
抽象クラスはこの問題を解決するためのものです。メソッドのシグネチャ(宣言)を定義し、具体的な実装はサブクラスに任せることができます。
概念の説明:抽象クラスは、通常のクラスとインターフェースの間に位置する型です。インターフェースの特徴(メソッドシグネチャの定義)と、クラスの特徴(具体的なメソッド実装やコンストラクタを持つことができる)の両方を兼ね備えています。
抽象クラスの基本
使用abstractabstract キーワードで抽象クラスと抽象メソッドを宣言します。
抽象クラスは、抽象メソッドと具象メソッド(実装済みのメソッド)を含むことができます。
例
// 抽象クラスは直接インスタンス化できず、基底クラスとしてのみ使用できる
abstract class Animal {
// 動物の名前プロパティ
name: string;
// コンストラクタ
constructor(name: string) {
this.name = name;
}
// 抽象メソッド:abstract で修飾され、メソッド本体がない
// サブクラスはこのメソッドを必ず実装する必要がある
abstract speak(): void;
// 具象メソッド:具体的な実装があるメソッド
// サブクラスはオーバーライドせずにそのまま継承して使用できる
move(): void {
console.log(this.name + " 移動しています");
}
}
// 抽象クラスをインスタンス化しようとするとエラーになる
// var animal = new Animal("动物"); // エラー:抽象クラスはインスタンス化できない
// Animal を継承する Dog クラスを定義する
class Dog extends Animal {
// サブクラスは親クラスの抽象メソッド speak() を必ず実装する
speak(): void {
console.log(this.name + " ワンワン!");
}
}
// Dog インスタンスを作成する
var dog = new Dog("旺财");
dog.speak(); // サブクラスで実装されたメソッドを呼び出す
dog.move(); // 親クラスの具象メソッドを継承する
実行結果:
旺财 ワンワンワン! 旺财 移動中
説明:抽象メソッドにはメソッド本体がありません(メソッドシグネチャのみ)。サブクラスはこのメソッドを実装する必要があります。具象メソッドには具体的な実装があり、サブクラスはそのまま継承して使用できます。
抽象メソッド
抽象メソッドとは、抽象クラス内で宣言のみ行われ、実装されないメソッドです。
サブクラスは抽象クラスを継承した後、すべての抽象メソッドを実装しなければなりません。実装しないとエラーになります。
例
abstract class Shape {
// 抽象メソッド:宣言のみで、実装がない
// サブクラスはこれら2つのメソッドを必ず実装する必要がある
abstract area(): number;
abstract perimeter(): number;
// 具象メソッド:抽象メソッドを使用できる
// このメソッドは抽象メソッドを呼び出している。サブクラスは継承後に正常に使用できる
describe(): void {
console.log("面積: " + this.area().toFixed(2));
}
}
// Shape を継承する長方形クラスを定義する
class Rectangle extends Shape {
// 長方形の幅
width: number;
// 長方形の高さ
height: number;
// コンストラクタ
constructor(width: number, height: number) {
super(); // 親クラスのコンストラクタを呼び出す
this.width = width;
this.height = height;
}
// 抽象メソッドの実装:面積を計算する
area(): number {
return this.width * this.height;
}
// 抽象メソッドの実装:周囲の長さを計算する
perimeter(): number {
return 2 * (this.width + this.height);
}
}
// 長方形インスタンスを作成する
var rect = new Rectangle(4, 5);
// 継承した describe メソッドを呼び出す。内部でサブクラスの area メソッドが呼び出される
rect.describe();
console.log("周囲: " + rect.perimeter());
実行結果:
面积: 20.00 周长: 18
ヒント:具象メソッドは抽象メソッドを呼び出すことができます。これは、具象メソッドが呼び出される時点ではサブクラスが抽象メソッドを実装済みであるため、正常に実行できるからです。
型としての抽象クラス
抽象クラスはパラメータの型として使用できます。
つまり、関数は抽象クラスの任意のサブクラスのインスタンスを受け取ることができ、ポリモーフィズムが実現されます。
例
abstract class Animal {
// 抽象メソッド:すべての動物は鳴き声を発する
abstract speak(): void;
}
// Animal を継承する Cat クラスを定義する
class Cat extends Animal {
// 抽象メソッドを実装する
speak(): void {
console.log("ニャーニャー!");
}
}
// Animal を継承する Dog クラスを定義する
class Dog extends Animal {
// 抽象メソッドを実装する
speak(): void {
console.log("ワンワン!");
}
}
// 関数のパラメータ型を抽象クラス Animal として定義する
// この関数は Animal の任意のサブクラスのインスタンスを受け取ることができる
function makeSpeak(animal: Animal): void {
animal.speak();
}
// 異なるサブクラスのインスタンスを渡すことで、異なる動作を実現する(ポリモーフィズム)
makeSpeak(new Cat());
makeSpeak(new Dog());
// 抽象クラス型の配列:異なるサブクラスのインスタンスを格納できる
var animals: Animal[] = [new Cat(), new Dog()];
実行結果:
ニャンニャンニャン! ワンワンワン!
ポリモーフィズム:抽象クラスを型として使用する場合、実際に実行されるのはサブクラスのメソッド実装です。これがオブジェクト指向のポリモーフィズムの特徴です。
抽象クラスとインターフェースの違い
抽象クラスとインターフェースはどちらも型の仕様を定義するために使用されますが、いくつかの重要な違いがあります。
これらの違いを理解することは、実際の開発で正しい選択をするのに役立ちます。
| 特徴 | 抽象クラス | インターフェース |
|---|---|---|
| インスタンス化 | 直接インスタンス化できない | 直接インスタンス化できない |
| メソッド実装 | 具体的な実装を持つことができる | 宣言のみ(TypeScript 3.6+ ではデフォルト実装を持つことができる) |
| メンバー修飾子 | public、protected、private を追加できる | readonly のみ持てる |
| 継承 | 単一継承(extends できるクラスは1つのみ) | 多重実装(implements で複数のインターフェースを実装できる) |
| コンストラクタ | コンストラクタを持つことができる | コンストラクタを持つことができない |
選択のアドバイス:コードを共有する必要がある場合は抽象クラスを使用します。仕様や契約を定義する必要がある場合はインターフェースを使用します。多重継承が必要な場合もインターフェースを使用します。
完全な例:決済システム
以下は、抽象クラスを使用して実装した決済システムの例です。
この例は、抽象クラスの実際のプロジェクトでの応用を示しています。
例
abstract class Payment {
// 抽象メソッド:支払いを処理する。サブクラスで必ず実装する
abstract process(amount: number): boolean;
// 具象メソッド:支払い情報を検証する
// すべてのサブクラスがこのメソッドを使用できる
validate(): void {
console.log("支払い情報を検証します");
}
}
// クレジットカード決済クラス
class CreditCardPayment extends Payment {
// クレジットカード番号
cardNumber: string;
// コンストラクタ
constructor(cardNumber: string) {
super(); // 親クラスのコンストラクタを呼び出す
this.cardNumber = cardNumber;
}
// 抽象メソッドの実装:クレジットカード決済を処理する
process(amount: number): boolean {
console.log("クレジットカード決済を処理: " + amount);
return true;
}
}
// PayPal 決済クラス
class PayPalPayment extends Payment {
// PayPal メールアドレス
email: string;
// コンストラクタ
constructor(email: string) {
super();
this.email = email;
}
// 抽象メソッドの実装:PayPal 決済を処理する
process(amount: number): boolean {
console.log("PayPal 決済を処理: " + amount);
return true;
}
}
// ポリモーフィズムを使用してさまざまな支払い方法を処理する
var payments: Payment[] = [
new CreditCardPayment("1234"),
new PayPalPayment("[email protected]")
];
// 各支払い方法を繰り返し処理する
for (var _i = 0, payments_1 = payments; _i < payments_1.length; _i++) {
var payment = payments_1[_i];
// 継承した検証メソッドを呼び出す
payment.validate();
// サブクラスの処理メソッドを呼び出す
payment.process(100);
}
実行結果:
验证支付信息 处理信用卡支付: 100 验证支付信息 处理 PayPal 支付: 100
適用シナリオ:抽象クラスは、フレームワーク設計やビジネスロジックの階層化によく使用されます。例えば、決済処理、ユーザー認証、データ永続化などのシナリオです。
注意事項
- インスタンス化できない:抽象クラスは直接 new でインスタンスを作成できず、サブクラスの継承を通す必要があります
- 抽象メソッドを実装する必要があります:サブクラスがすべての抽象メソッドを実装しない場合、エラーになります
- コンストラクタを持つことができます:抽象クラスはコンストラクタを定義でき、サブクラスは super() を呼び出す必要があります
- 単一継承:1つのクラスは1つの抽象クラスのみ継承できます(単一継承)
- アクセス修飾子:抽象メソッドは public、protected 修飾子を使用できます
ベストプラクティス:複数のクラスがコードやロジックを共有する必要がある場合は、抽象クラスを使用します。仕様や契約のみを定義する必要がある場合は、インターフェースを使用します。
まとめ
抽象クラスは TypeScript のオブジェクト指向プログラミングの重要な構成要素です。
- abstract キーワード:抽象クラスやメソッドを宣言するために使用します
- インスタンス化できない:サブクラスの継承を通してのみ使用できます
- 抽象メソッド:サブクラスが必ず実装する必要があるメソッド
- 具象メソッド:サブクラスが直接継承して使用できる、実装のあるメソッド
- テンプレートメソッドパターン:抽象クラスが骨格を定義し、サブクラスが具体的な実装を提供します
その他の拡張提案:クラス階層を設計する際は、コードを共有し仕様を定義するために抽象クラスの使用を優先的に検討してください。