多態と static
このレッスンで分かること
- 同じ名前のメソッドを種類ごとに用意すると、使う側から
ifが消えますstaticを付けたものはインスタンスではなくクラスに付き、値がそのまま残り続けます
呼ぶ側の if が、種類の数だけ増える
前の回で、種類ごとにクラスを分けました。分けたあと、使う側がこう書いていると苦しくなります。
JavaScript
const lines = notes.map((note) => {
if (note instanceof PinnedNote) {
return "重要 " + note.topic + " " + note.minutes + " 分";
}
return note.topic + " " + note.minutes + " 分";
});種類が 3 つになれば分岐が 3 本並びます。しかも同じ分岐を、一覧の描画、件数の集計、並び替えと、場所ごとに書き写すことになります。1 か所を直し忘れると、そこだけ古い表示が残ります。
同じ名前のメソッドを両方のクラスに持たせておけば、分岐はクラスの側に 1 回書くだけで済みます。
JavaScript
const lines = notes.map((note) => note.render());どちらの render が動くかは、そのインスタンスがどのクラスから作られたかで自動的に決まります。呼ぶ側は種類を知らなくてよくなります。
効くのは、メソッド名が完全に同じで、引数と戻り値の形もそろっているときだけです。片方が文字列を返し、もう片方がオブジェクトを返すなら、結局呼ぶ側で分岐することになります。名前だけそろえても意味がありません。
static はインスタンスではなくクラスに付く
ここまでのメソッドは new で作ったインスタンスから呼ぶものでした。static を付けると、クラス名から直接呼ぶものになります。
JavaScript
class Comment {
static total = 0;
constructor(body) {
this.body = body;
Comment.total += 1;
}
static countLabel() {
return `これまでに ${Comment.total} 件`;
}
}
new Comment("はじめまして");
new Comment("参考になりました");
console.log(Comment.total); // 2
console.log(Comment.countLabel()); // これまでに 2 件| 書き方 | 誰に付くか | 呼び方 |
|---|---|---|
render() {} | インスタンス | note.render() |
static countLabel() {} | クラス | Comment.countLabel() |
static total = 0 | クラス | Comment.total |
total は Comment に 1 つだけあります。comment.total と書いても undefined です。インスタンスからは見えません。
static のメソッドは new より前に呼べるので、「どのクラスで作るか」の判断をクラス自身に持たせる窓口にもできます。呼ぶ側は子クラスの名前を知らずに済み、あとで種類が増えても直すのはその窓口の中だけです。
static に置いた数は、次の呼び出しにも残る
static のフィールドは、プログラムが動いている間ずっと生き続けます。同じ関数を 2 回呼ぶと、2 回目は前回に増えた分を引きずります。
JavaScript
console.log(Comment.total); // 2
new Comment("また来ました");
console.log(Comment.total); // 3第3章で、何回呼んでも同じ結果になる関数のことを学びました。static を使うとその性質は簡単に崩れます。数え直したいなら、数える処理の先頭で 0 に戻すと決めておきます。戻す場所を末尾に置くと、途中で return したときに素通りします。
staticに持たせてよいのは、クラス全体で 1 つと決まっているものだけです。作った件数や既定の設定値がそれにあたります。個々のインスタンスに関わる値を置くと、あとから作ったほうに上書きされます。
よくある間違い
- インスタンスから
staticを呼ぶ —comment.countLabel()はTypeErrorです。staticはクラスに付いているので、クラス名から呼びます staticの中のthisをインスタンスのつもりで使う —staticのthisはクラス自身です。this.bodyと書いてもundefinedにしかなりません- 上書きしたつもりでメソッド名を間違える —
renderをrendarと書いても、その場ではエラーになりません。子のインスタンスで呼ぶと親のほうが静かに動き、印の付かない行だけが混ざります
要件
- class Work は static created を 0 から始め、constructor が呼ばれるたびに Work.created を 1 増やす。format() は
タイトル N 回を返す - static from(record) は record.views が 100 以上なら PopularWork、それ以外なら Work のインスタンスを返す
- class PopularWork extends Work は format() を上書きし、super.format() の後ろに半角スペースと
人気を足した文字列を返す - function buildWorkReport(records) を定義する。先頭で Work.created を 0 に戻してから Work.from で作り、{ lines, created } を返す
入出力例
buildWorkReport([{"title":"プロフィールサイト","views":120},{"title":"学習ログ","views":8}]) → {"created":2,"lines":["プロフィールサイト 120 回 人気","学習ログ 8 回"]}
buildWorkReport([]) → {"created":0,"lines":[]}
buildWorkReport([{"title":"作品ギャラリー","views":100}]) → {"created":1,"lines":["作品ギャラリー 100 回 人気"]}
buildWorkReport([{"title":"自己紹介カード","views":99}]) → {"created":1,"lines":["自己紹介カード 99 回"]}
buildWorkReport([{"title":"プロフィールサイト","views":120},{"title":"作品ギャラリー","views":200},{"title":"スキル一覧の表","views":300}]) → {"created":3,"lines":["プロフィールサイト 120 回 人気","作品ギャラリー 200 回 人気","スキル一覧の表 300 回 人気"]}