1継承の最小形 — 全部引き継ぐ
extends と書くだけで、子クラスは親クラスのプロパティとメソッドをすべて引き継ぎます。Dog には何も書いていないのに、親の cry() がそのまま呼べることを確認してください。instanceof が示すとおり、Dog のインスタンスは Animal のインスタンスでもあります。
class Animal {
cry(): string {
return "鳴き声";
}
}
// 何も書かなくても、親のメンバーをすべて引き継ぐ
class Dog extends Animal {}
const pochi = new Dog();
console.log(pochi.cry());
console.log(pochi instanceof Animal); // 親のインスタンスでもある
2super() — 親のコンストラクタを呼ぶ
子クラスに独自のコンストラクタを書く場合は、先頭で super(...) を呼んで親の初期化を先に済ませるのが決まりです。super を呼ばない、または super より前に this に触るとコンパイルエラーになります。共通のプロパティは親に、追加のプロパティは子に、という分担が基本形です。
class Product {
productName: string;
constructor(productName: string) {
this.productName = productName;
}
}
class Book extends Product {
author: string;
constructor(productName: string, author: string) {
super(productName); // 先頭で親のコンストラクタを呼ぶ
this.author = author;
}
}
const novel = new Book("吾輩は猫である", "夏目漱石");
console.log(novel.productName + " / " + novel.author);
3オーバーライド — 親のメソッドを上書きする
親と同名のメソッドを子クラスに書くと、子の実装で上書き(オーバーライド)されます。super.メソッド名() で親の実装も呼び出せるので、「親の処理に一手間足す」拡張がきれいに書けます。ログの整形に時刻や接頭辞を足す、といった場面が実務の典型です。
class Logger {
format(message: string): string {
return "[LOG] " + message;
}
}
class TimestampLogger extends Logger {
// 同名メソッドを書くと上書き(オーバーライド)になる
format(message: string): string {
return "10:30 " + super.format(message); // 親の実装も呼べる
}
}
console.log(new Logger().format("起動"));
console.log(new TimestampLogger().format("起動"));
4protected — 継承先にだけ内部を開く
private のプロパティは子クラスからも触れませんが、protected なら継承先からは触れます。親が管理する内部状態を、子クラスで追加したメソッドから直接操作したいときに使う、継承とセットで覚える修飾子です。外部から直接触れない点は private と同じです。
class Counter {
protected current: number = 0; // 継承先からは触れる
increment(): void {
this.current = this.current + 1;
}
value(): number {
return this.current;
}
}
class StepCounter extends Counter {
addStep(size: number): void {
this.current = this.current + size; // protected なので OK
}
}
const steps = new StepCounter();
steps.increment();
steps.addStep(10);
console.log(steps.value()); // 11
// console.log(steps.current); // 外からはエラー
5基底Repositoryクラス — 共通機能を親にまとめる
保存や件数取得のような共通処理を基底クラスに実装し、データの種類ごとに違う処理だけを子クラスに書く、実務の定番パターンです。データ種別が増えても基底クラスを extends して差分を書くだけで済みます。protected にした rows を子クラスのメソッドが読んでいる点にも注目してください。
class BaseRepository {
protected rows: { id: number; title: string }[] = [];
save(id: number, title: string): void {
this.rows.push({ id: id, title: title });
}
count(): number {
return this.rows.length;
}
}
// 共通の save / count は親のまま、記事ならではの処理だけ足す
class ArticleRepository extends BaseRepository {
titles(): string[] {
return this.rows.map(function (row) { return row.title; });
}
}
const articleRepo = new ArticleRepository();
articleRepo.save(1, "TypeScript入門");
console.log(articleRepo.count() + "件");
console.log(articleRepo.titles());