4日目のゴール
4日目のテーマも
「直接触らせない」=カプセル化。
1〜3日目であなたはすでに
フィールドを private にする
必要なものだけ getter / setter を出す
クラスの「壊れてはいけない約束(不変条件)」を中に閉じ込める
というところまで来ています。
4日目はここから一歩進んで、
「カプセル化と“外の世界”との付き合い方」 を扱います。
つまり、
「他のクラスからどう見えるか」まで含めてprivate と getter / setter を設計する 日です。
カプセル化は「自分だけの話」では終わらない
クラスは必ず“他人”と一緒に使われる
カプセル化は、
「このクラスの中身を守る」ための仕組みですが、
そのクラスは必ず 他のクラスと一緒に使われます。
ユーザー情報を持つ User は、
画面表示クラスやサービスクラスから使われる。
ポイントカード PointCard は、
決済処理クラスから使われる。
銀行口座 BankAccount は、
振込処理クラスから使われる。
4日目の視点はこうです。
「他のクラスから見たとき、
このクラスは“どう見えていてほしいか”?」
この視点で、private と getter / setter をもう一段整理していきます。
例題1:User と UserService の関係で考える
「中身を丸出しにするサービス」になっていないか
まず、シンプルな User を用意します。
public class User {
private final int id;
private String name;
private int age;
public User(int id, String name, int age) {
if (age < 0) throw new IllegalArgumentException("年齢は0以上");
this.id = id;
this.name = name;
this.age = age;
}
public int getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) {
if (age < 0) throw new IllegalArgumentException("年齢は0以上");
this.age = age;
}
}
Javaここまでは OK。
では、この User を扱うサービスクラスを考えます。
public class UserService {
public void birthday(User user) {
int age = user.getAge();
user.setAge(age + 1);
}
}
Java一見、普通です。
でも、ここで立ち止まってほしい。
UserService は、User の「年齢の持ち方」を知ってしまっています。
getAge() して +1 して setAge() する、
という 「年齢の更新ロジック」 を
サービス側が持ってしまっている。
もし将来、User が
「年齢ではなく生年月日で持つ」ように変わったらどうなるか?
User の中身だけでなく、UserService も修正が必要になります。
ここで効いてくるのが、
「振る舞いもクラスの中に閉じ込める」 という考え方です。
例題1の改善:User に「誕生日メソッド」を持たせる
「年齢を増やす」という意味を User の中に閉じ込める
UserService ではなく、User 自身に「誕生日」を表すメソッドを持たせます。
public class User {
private final int id;
private String name;
private int age;
public User(int id, String name, int age) {
if (age < 0) throw new IllegalArgumentException("年齢は0以上");
this.id = id;
this.name = name;
this.age = age;
}
public int getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void haveBirthday() {
this.age += 1;
}
}
Java外からはこう呼びます。
User user = ...;
user.haveBirthday();
Javaここでの変化は大きいです。
「年齢をどう増やすか」というロジックがUser の中に閉じ込められた。
UserService は、
「誕生日」という意味だけを呼び出せばよくなった。
もし将来、age をフィールドとして持たず、birthDate から計算するように変えても、haveBirthday() の中身だけ変えれば済みます。
「中身の表現」と「外からの使い方」を分離できている」
これがカプセル化と責務分担が噛み合った状態です。
重要ポイントの深掘り1:「値を取って、値を戻す」より「意味のあるメソッド」
getter / setter だけで会話していると、設計が“生データ寄り”になる
getX() して setX(...) する、
というパターンは、
「生データを外に出して、外でいじって、戻す」
というスタイルです。
これは最初のうちは分かりやすいですが、
中級に入ると、こういう問題が出てきます。
外側のクラスが、
そのフィールドの意味やルールを知りすぎる。
内部表現を変えたときに、外側のクラスも巻き込まれる。
そこで一段上の設計として、
「意味のあるメソッド」を増やしていく という方向があります。
User なら haveBirthday()
PointCard なら addPoint() と usePoint()
BankAccount なら deposit() と withdraw()
こういうメソッドは、
「中身を直接触らせない」だけでなく、
「意味だけを外に見せる」 役割を持ちます。
例題2:PointCard と「外側のクラス」の関係
外側が「計算役」になっていないか
PointCard をもう一度見ます。
public class PointCard {
private int point;
public PointCard() {
this.point = 0;
}
public int getPoint() {
return point;
}
public void addPoint(int amount) { ... }
public void usePoint(int amount) { ... }
}
Javaここまでは OK。
では、決済処理クラスを考えます。
public class PaymentService {
public void payWithPoint(PointCard card, int price) {
int point = card.getPoint();
if (point >= price) {
card.usePoint(price);
} else {
// 足りない分は現金、などの処理
}
}
}
Javaこれはまだ許容範囲ですが、
もし外側がどんどんこうなっていったら危険です。
getPoint() して
「ポイントが何円相当か」を外で計算して
「何ポイント使うか」を外で決めてusePoint() に渡す
外側が「ポイントの意味」を知りすぎている状態です。
ここで一歩進めると、
こういう設計もできます。
public class PointCard {
private int point;
public boolean canPay(int price) {
return point >= price;
}
public void pay(int price) {
if (!canPay(price)) {
throw new IllegalArgumentException("ポイント不足");
}
this.point -= price;
}
}
Java外側はこう書けます。
public class PaymentService {
public void payWithPoint(PointCard card, int price) {
if (card.canPay(price)) {
card.pay(price);
} else {
// 足りない分は現金、など
}
}
}
Javaポイントは、
「ポイントで支払えるかどうか」という判断も
「支払う」という処理も、
PointCard の中に閉じ込めた ことです。
外側は「意味」だけを呼び出す。
中身の計算やルールは、
クラス自身が責任を持つ。
これが、
カプセル化が「設計」として効き始める瞬間です。
重要ポイントの深掘り2:「外から見える顔」を意識してメソッドを選ぶ
そのクラスを“どういう存在として見せたいか”
4日目で意識してほしいのは、
「このクラスを、外からどう見える存在にしたいか」
という視点です。
User を
「年齢という数字を持つ入れ物」として見せたいのか、
「誕生日を迎える人」として見せたいのか。
PointCard を
「ポイントという整数を持つ入れ物」として見せたいのか、
「ポイントで支払うことができるカード」として見せたいのか。
BankAccount を
「残高という整数を持つ入れ物」として見せたいのか、
「入金・出金という操作を提供する口座」として見せたいのか。
後者を選ぶと、
自然とこうなります。
フィールドは privategetter / setter は最小限
代わりに「意味のあるメソッド」が増える
これが、
カプセル化 × オブジェクト指向らしい設計 です。
4日目の実践:自分のクラスに「意味メソッド」を足してみる
「get+set だけでやっている処理」を、クラスの中に移す
今日やってみてほしい練習は、とても具体的です。
自分のコードの中から、
「get して、計算して、set している」箇所を探してみてください。
例えば、
user.getAge() して +1 して setAge()card.getPoint() して - price して setPoint()account.getBalance() して - amount して setBalance()
こういう処理を見つけたら、
「それ、本当はそのクラスの中に置くべきじゃない?」
と問い直してみる。
そして、haveBirthday()pay(price)withdraw(amount)
のような「意味のあるメソッド」として
クラスの中に移してみる。
これをやるだけで、
外側のコードはスッキリし、
クラスの中には「ルール」と「意味」が集まります。
4日目で本当に掴んでほしいこと
4日目のテーマは、
「直接触らせない」を
「意味だけ触らせる」に進化させること でした。
private は、中身を守るための鍵。getter / setter は、窓口。
でも、
中級のカプセル化はそこから一歩進んで、
中身のルール(不変条件)をクラスの中に閉じ込める
外からは「意味のあるメソッド」だけを見せる
外側のクラスに「中身の事情」を漏らさない
という設計の話になります。
ここまで来ているあなたは、
もう「カプセル化を知っている人」ではなく、
「カプセル化を設計に使える人」 です。
この感覚を持ったまま、
5日目以降は
「カプセル化 × コレクション」「カプセル化 × 複数クラスの連携」
みたいな、さらに一段複雑な世界にも
自然に入っていけます。
