1抽象クラスの最小形 — new できない土台
abstract を付けたクラスは new できず、継承されるためだけに存在します。abstract メソッドは名前と型だけを宣言し、実装は子クラスに義務付けます。実装を忘れると子クラスの定義時点でエラーになるのがポイントです。
abstract class Greeter {
abstract greet(): string; // 中身は子クラスが必ず書く
}
class MorningGreeter extends Greeter {
greet(): string {
return "おはようございます";
}
}
const greeter = new MorningGreeter();
console.log(greeter.greet());
// const base = new Greeter(); // 抽象クラスは new できずエラー
2共通実装 + abstract — 流れは親、中身は子
共通の処理(printAll)は親が実装を持ち、クラスごとに違う部分(body)だけを abstract にしています。「処理の流れは親が決め、中身は子が埋める」というテンプレートメソッドと呼ばれる設計の最小形です。帳票やレポートの生成処理で実務でもよく見ます。
abstract class DailyReport {
abstract body(): string; // ここだけレポートごとに違う
// 見出しを付けて出力する流れは全レポート共通
printAll(): void {
console.log("=== 日報 ===");
console.log(this.body());
}
}
class SalesReport extends DailyReport {
body(): string {
return "本日の売上は 12,000円 でした";
}
}
new SalesReport().printAll();
3abstract プロパティ — 値の用意も義務にできる
abstract にできるのはメソッドだけでなくプロパティもです。親の describe() は、まだ実体のない this.planName を使って書けています。「子クラスが必ず持つ」という約束があるからこそ成立する共通実装です。
abstract class PricePlan {
abstract planName: string; // プロパティも abstract にできる
abstract monthlyFee: number;
describe(): string {
return this.planName + ": 月額" + this.monthlyFee + "円";
}
}
class ProPlan extends PricePlan {
planName = "Pro";
monthlyFee = 980;
}
console.log(new ProPlan().describe());
4抽象クラスを型として使う
抽象クラスは変数や配列の型としても使えます。Shape[] に入れてしまえば、使う側は「area() がある」ことだけ知っていればよく、中身が Square か Circle かを気にせず一律に処理できます。「同じ操作を持つ一群のクラス」をまとめて扱う基本形です。
abstract class Shape {
abstract area(): number;
}
class Square extends Shape {
side = 3;
area(): number { return this.side * this.side; }
}
class Circle extends Shape {
radius = 2;
area(): number { return Math.round(this.radius * this.radius * 3.14); }
}
// 変数の型として抽象クラスを使うと、子クラスをまとめて扱える
const shapes: Shape[] = [new Square(), new Circle()];
for (const shape of shapes) {
console.log(shape.area());
}
5通知チャネルの抽象化
通知チャネルの抽象化は abstract の代表的な実務例です。宛名の整形のような共通処理は親の send() が持ち、メール・チャットなど手段ごとに違う部分だけを deliver() として子に書かせます。チャネルを追加したくなったら、クラスを1つ増やすだけで済みます。
abstract class NotificationChannel {
abstract deliver(body: string): string; // 送信手段ごとに違う部分
send(userName: string, body: string): void {
console.log(this.deliver(userName + "さん: " + body));
}
}
class MailChannel extends NotificationChannel {
deliver(body: string): string { return "メール送信 -> " + body; }
}
class ChatChannel extends NotificationChannel {
deliver(body: string): string { return "チャット送信 -> " + body; }
}
const channels: NotificationChannel[] = [new MailChannel(), new ChatChannel()];
for (const channel of channels) {
channel.send("Taro", "会議は15時からです");
}