7日目のゴール
7日目のテーマは
「直接触らせない」を 自分の設計ポリシーとして言語化すること です。
ここまでで、あなたはすでに
- フィールドを
privateにする意味 - 必要なものだけ
getter / setterを出す感覚 - クラスの「壊れてはいけない約束(不変条件)」を中に閉じ込める
- 外からは「意味のあるメソッド」だけを見せる
- クラス同士の関係でも“中身を触りすぎない”ようにする
を体で経験してきました。
7日目は、それを
「なんとなく分かる」から
「自分の言葉で説明できる」 まで持ち上げます。
カプセル化アプリを“設計の目”で振り返る
1クラスの中で起きていたこと
まず、1クラスの中で何をしてきたかを整理します。
private でフィールドを隠す。public な getter / setter を、必要なものだけ出す。setter の中で値のチェックをして、不変条件を守る。
「ただ代入するだけの setter」は作らず、「ルールの門番」として設計する。
PointCard なら、
ポイントは常に 0 以上
マイナスにはならない
残高以上は使えない
という約束を、
コンストラクタ・addPoint・usePoint の中に閉じ込めました。
BankAccount なら、
残高は常に 0 以上
初期残高も 0 以上
入金額・出金額は 1 以上
残高以上は引き出せない
という約束を、
コンストラクタ・deposit・withdraw の中に閉じ込めました。
どちらも、
「このクラスのインスタンスは、常にこういう状態であるべき」
という不変条件を、
クラス自身が守る形になっています。
これが「直接触らせない」の第一段階でした。
クラス同士がつながったときに起きていたこと
次に、クラス同士の関係です。
BankAccount と TransferService。
TodoList と UI。
User と UserService。
最初は、外側のクラスがgetBalance() や getPoint() で中身を取り出して、
外で計算して、set...() や操作メソッドに渡していました。
そこから一歩進んで、
「振込できるか?」は BankAccount に聞く。
「ポイントで支払えるか?」は PointCard に聞く。
「誕生日を迎える」は User のメソッドにする。
「タスクが多すぎるか?」は TodoList に聞く。
というふうに、
「意味のあるメソッド」をクラス側に増やしていきました。
外側のクラスは、
中身の事情(残高の計算、ポイントのルール、リストのサイズなど)を
知りすぎない。
代わりに、
「振込する」「支払う」「誕生日を迎える」「全部消す」
といった “意味だけ” を呼び出す。
これが、
「直接触らせない」をクラス間の関係にまで広げた形でした。
自分の「カプセル化ポリシー」を言葉にしてみる
1クラス1不変条件
まず、こういう軸を持てていたら強いです。
自分は、クラスを作るとき、
まず「このクラスのインスタンスは、常にどういう状態であるべきか」を決める。
その約束(不変条件)を、コンストラクタとメソッドの中で必ず守らせる。
フィールドはprivateにし、約束を破るような操作は外に出さない。
PointCard なら「point は常に 0 以上」。
BankAccount なら「balance は常に 0 以上」。
User なら「id は一度決まったら変わらない」。
この「一文の約束」を、
クラスの中で完結させる のが、あなたのポリシーです。
getter / setter を「自動生成」ではなく「設計」する
次に、こういう軸。
フィールドごとに、「外から読めていいか」「外から変えられていいか」を考える。
読めていいものには getter を作る。
変えられていいものには、ルールを守る setter か意味のあるメソッドを作る。
変えられてほしくないものには setter を作らず、コンストラクタだけで設定する。
User の id は final + getter のみ。
User の name は getter + rename(...)。
PointCard の point は getter のみで、
変更は addPoint と usePoint 経由。
「全部に setter」ではなく、
「変わっていいものだけ、意味を持った窓口を用意する」
というスタイルです。
「生データではなく意味で会話する」
もう一つ、大事な軸。
外側のクラスが、
getX()して計算してsetX(...)するようなコードを見つけたら、
それを「意味のあるメソッド」としてクラスの中に移すことを考える。
外からは「誕生日を迎える」「支払う」「振込する」などの意味だけを呼び出す。
User の haveBirthday()。
PointCard の pay(price)。
BankAccount の canWithdraw(amount) + withdraw(amount)。
TodoList の clear() や isTooMany(limit)。
こういうメソッドが増えるほど、
外側のコードはシンプルになり、
ルールと意味はクラスの中に集まります。
「直接触らせない」=「意味だけ触らせる」
という感覚が、ここにあります。
例題で「ポリシー」を当てはめてみる
小さな「在庫」クラスを設計してみる
最後に、7日目らしく
新しいクラスを一つ設計してみましょう。
テーマは「在庫」。
やりたいことは、
在庫数を持つ
入荷で増える
出荷で減る
在庫はマイナスにならない
まず、日本語で不変条件を書きます。
このクラスの stock は、常に 0 以上である。
それをコードに落とします。
public class Stock {
private int quantity;
public Stock(int initialQuantity) {
if (initialQuantity < 0) {
throw new IllegalArgumentException("初期在庫は 0 以上");
}
this.quantity = initialQuantity;
}
public int getQuantity() {
return quantity;
}
public void receive(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("入荷数は 1 以上");
}
this.quantity += amount;
}
public void ship(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("出荷数は 1 以上");
}
if (amount > quantity) {
throw new IllegalArgumentException("在庫不足");
}
this.quantity -= amount;
}
public boolean canShip(int amount) {
return amount > 0 && amount <= quantity;
}
}
Javaここに、あなたのポリシーが全部乗っています。
フィールドは private。
不変条件「在庫は常に 0 以上」をクラスの中で守る。setQuantity は作らない。
外からは receive・ship・canShip という意味だけを見せる。
これが、
「直接触らせない」を自分の設計として使っている状態 です。
7日目で本当に伝えたいこと
7日間の「カプセル化アプリ」で、
あなたはもう
private と getter / setter の文法を知っている人
ではなく、
「クラスの中身を守るために、
何を見せて何を隠すかを意図的に決められる人」
になっています。
フィールドは private。
不変条件をクラスの中に閉じ込める。
必要なものだけ、意味を持った窓口として公開する。
外側のクラスには「生データ」ではなく「意味」で会話させる。
これが、あなたの
「直接触らせない」=カプセル化ポリシーです。
あとは、この軸を
別のアプリ(家計簿、ゲーム、ToDo、日記…)にも
どんどん持ち込んでみてください。
どのクラスにも、
「このクラスの約束は何か?」
「それを守るために、何を隠して何を見せるか?」
と問いかけていく。
その問いを持ち続ける限り、
あなたの Java コードは
確実に“中級”から“その先”に進んでいきます。
