2日目のゴールと今日やること
2日目のテーマは
「class の“中身の振る舞い”をちゃんと設計して、オブジェクトを“ただのデータ”から“自分で動ける存在”にすること」です。
キーワードは昨日と同じく
class/カプセル化/責務分離
ですが、今日は特に
「メソッドの設計」
「状態をどう変えるか」
「“どこに書くべき処理か”を見極める」
ここを一段深くやっていきます。
まずは 1日目の Memo / MemoList を少し思い出す
シンプルな Memo と MemoList のおさらい
昨日のイメージはこんな感じでした。
class Memo {
#text;
constructor(text) {
this.#text = text;
}
getText() {
return this.#text;
}
setText(newText) {
if (typeof newText !== "string") {
console.warn("文字列以外はセットできません");
return;
}
this.#text = newText;
}
}
class MemoList {
#memos;
constructor() {
this.#memos = [];
}
add(text) {
const memo = new Memo(text);
this.#memos.push(memo);
}
getAll() {
return this.#memos;
}
}
JavaScriptここまでは、
Memo は「1つのメモ」を表す。
MemoList は「メモの集まり」を管理する。
という“役割の分け方”を体感してもらいました。
今日はここに「振る舞い(メソッド)」を足していきます。
「ただのデータ入れ」から「自分で動けるオブジェクト」へ
データだけ持つオブジェクトは、まだ“構造体”レベル
例えば、こういうのは「ただのデータ入れ」です。
const memo = {
text: "牛乳を買う",
done: false
};
JavaScriptこれはこれで使えますが、
「完了にする」「未完了に戻す」などの“振る舞い”は
外側の関数で書くことになります。
function toggleDone(m) {
m.done = !m.done;
}
JavaScriptこれをクラスでやると、
「メモ自身が自分の状態を変える」ように書けます。
状態と振る舞いをセットで持つ Memo クラス
完了フラグを持たせてみる
まずは Memo に「完了フラグ」を足します。
class Memo {
#text;
#done;
constructor(text) {
this.#text = text;
this.#done = false;
}
getText() {
return this.#text;
}
setText(newText) {
if (typeof newText !== "string") {
console.warn("文字列以外はセットできません");
return;
}
this.#text = newText;
}
isDone() {
return this.#done;
}
markDone() {
this.#done = true;
}
markUndone() {
this.#done = false;
}
}
JavaScriptここでのポイントは、
完了しているかどうか(#done)は外から直接触れない。
代わりに、isDone / markDone / markUndone という“振る舞い”を用意する。
つまり、
「状態(#done)と、それをどう変えるか(メソッド)をセットでクラスに閉じ込める」
これがオブジェクト指向っぽさの一歩目です。
カプセル化を“状態+振る舞い”の単位で考える
なぜ #done を外から触らせないのか
もし #done を public にしてしまうと、
どこからでもこう書けてしまいます。
memo.done = "はい"; // 真偽値じゃないものを入れてしまえる
JavaScriptこれを許すと、
「done は true / false のはずなのに、どこかで文字列が入ってバグる」
という未来が待っています。
カプセル化の本質は、
「そのクラスが“こうあってほしい”というルールを、
外から壊されないようにする」
ことです。
Memo の場合、
text は文字列であってほしい。
done は true / false であってほしい。
だから、
text は setText の中でチェックする。
done は markDone / markUndone だけが変える。
という設計にします。
責務分離を“メソッドレベル”で見る
どの処理を Memo に書くべきか?
例えば、こんな処理を考えます。
「完了しているメモには [x]、
未完了のメモには [ ] を付けて表示したい」
これをどこに書くべきか?という話です。
パターンA:外側の関数でやる。
function printMemo(memo) {
const mark = memo.isDone() ? "[x]" : "[ ]";
console.log(mark, memo.getText());
}
JavaScriptパターンB:Memo クラスに「自分を表示する」メソッドを持たせる。
class Memo {
// さっきの定義に追加
print() {
const mark = this.#done ? "[x]" : "[ ]";
console.log(mark, this.#text);
}
}
JavaScriptどちらもアリですが、
オブジェクト指向の考え方に寄せるなら、
「自分の表示の仕方は、自分が知っている」
と考えて Memo に print() を持たせるのは、かなり自然です。
責務分離をメソッドレベルで言い換えると、
「この処理は“誰の仕事”か?」
を常に自問することです。
完了フラグの扱い → Memo の仕事。
メモの集まりの検索 → MemoList の仕事。
画面(DOM)への描画 → 画面担当のクラス or 関数の仕事。
こうやって“仕事の担当者”を決めていきます。
MemoList に「振る舞い」を足していく
完了しているメモだけを取り出す
MemoList にも、
「集まりとしての振る舞い」を足していきます。
class MemoList {
#memos;
constructor() {
this.#memos = [];
}
add(text) {
const memo = new Memo(text);
this.#memos.push(memo);
}
getAll() {
return this.#memos;
}
getDoneMemos() {
return this.#memos.filter(memo => memo.isDone());
}
getUndoneMemos() {
return this.#memos.filter(memo => !memo.isDone());
}
}
JavaScriptここでの責務分離はこうです。
「完了しているかどうか」は Memo が知っている(isDone)。
「完了しているメモだけを集める」のは MemoList の仕事。
MemoList は「集まりとしての操作」を担当し、
個々の Memo の中身には踏み込みすぎないようにします。
小さなシナリオで“責務の流れ”を追ってみる
実際に動かしてみるコード
const list = new MemoList();
list.add("牛乳を買う");
list.add("本を読む");
list.add("メールを返す");
const all = list.getAll();
all[1].markDone(); // 2番目のメモを完了にする
console.log("=== すべてのメモ ===");
all.forEach(m => m.print());
console.log("=== 完了したメモ ===");
list.getDoneMemos().forEach(m => m.print());
console.log("=== 未完了のメモ ===");
list.getUndoneMemos().forEach(m => m.print());
JavaScriptここで起きていることを、責務の流れで見るとこうです。
MemoList は「メモの集まり」を持っている。
Memo は「自分の text と done 状態」を持っている。
完了にするのは Memo の仕事(markDone)。
完了しているメモだけ集めるのは MemoList の仕事(getDoneMemos)。
表示の仕方は Memo の仕事(print)。
誰が何を担当しているかが、
かなりハッキリしてきています。
カプセル化を“守りたいルール”から逆算する
ルールを日本語で書いてから、class に落とす
2日目で一番深掘りしてほしいのはここです。
クラスを設計するときは、
まず「守りたいルール」を日本語で書き出す。
例えば Memo なら、
text は必ず文字列。
done は true / false だけ。
外から勝手に done を書き換えられたくない。
表示の形式は Memo の中で決めたい。
このルールを守るために、
#text / #done を private にする。
setText で型チェックをする。
markDone / markUndone だけが done を変える。
print メソッドで表示形式を統一する。
というふうに、
カプセル化とメソッド設計が“ルールから逆算されている”状態が理想です。
2日目のまとめ:class を「箱」ではなく「役割」として見る
今日の本質を一言で言うと、
「class は“データの箱”ではなく、“状態と振る舞いをまとめた役割”だ」
ということです。
Memo は「1つのメモ」という役割。
MemoList は「メモの集まりを管理する」という役割。
それぞれが、
自分の状態をカプセル化し、
自分の責任範囲の処理だけを引き受ける。
この感覚が持てていれば、
3日目以降で「画面(DOM)とクラスをつなぐ」「イベントとクラスを組み合わせる」
といった、より“アプリっぽい”世界に進んでも、
クラス設計で迷子になりにくくなります。
もし余裕があれば、
Memo に「重要フラグ(priority)」を足してみる
MemoList に「重要なメモだけを返すメソッド」を足してみる
など、自分なりに“ルールを決めて → カプセル化して → 責務を分ける”
という流れを遊んでみてください。
そこから先は、もう立派なクラス設計の練習です。


