2日目のゴール
2日目のテーマも
「直接触らせない」=カプセル化。
1日目はprivate と getter / setter の「基本の形」と
「なぜ直接触らせないのか」という感覚をつかみました。
2日目はそこから一歩進んで、
「どこまで隠すか」「どこまで見せるか」 を
意識して設計できるようになることを目指します。
もう一度整理:なぜ“直接触らせない”のか
「値を守る」だけじゃなく「ルールを守る」ため
1日目の User クラスを思い出してください。
public class User {
public String name;
public int age;
}
Javaこれは「誰でも、どこからでも、どんな値でも入れられる」状態でした。
age = -10name = null
全部通ってしまう。
private にして setter を通すようにすると、
「値を守る」ことができるようになりました。
2日目で意識してほしいのは、
「値」ではなく「ルール」を守る という視点です。
年齢は 0 以上
ポイントはマイナスにならない
残高以上は引き出せない
こういう「そのクラスが守るべきルール」を
クラスの中に閉じ込める のがカプセル化です。
getter / setter を「なんとなく全部作る」から卒業する
自動生成された getter / setter の罠
IDE でクラスを作ると、
フィールドを選んで「Generate Getter/Setter…」で
一気に全部作れますよね。
public class User {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
Javaこれは「書き方の練習」にはいいですが、
2日目からは、ここに一歩ツッコミを入れたい。
本当に全部 setter が必要か?
本当に外から自由に変えられていいのか?
「全部に getter / setter を付ける=カプセル化」ではない
ということを、今日ははっきりさせます。
例題1:名前は変えられていいけど、ID は変えられたくない
「変えていいもの」と「変えちゃダメなもの」を分ける
ユーザーを表すクラスを、もう少し現実寄りにしてみます。
public class User {
private final int id;
private String name;
private int age;
public User(int id, String name, int age) {
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ここでのポイントは、id には setter を用意していない ことです。
ID は「一度決まったら変わらないもの」として扱いたい。
だから final にして、コンストラクタでだけ設定する。
外からは getId() で読むことはできるけれど、setId(...) は存在しない。
これが
「何でもかんでも setter を作らない」 という設計です。
例題2:PointCard をもう一段“カプセル化らしく”する
「残高を直接いじらせない」から「操作だけを公開する」へ
1日目の 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ここで、あえてこういう setter を追加してみます。
public void setPoint(int point) {
this.point = point;
}
Java一見「便利そう」に見えますが、
これを許すと、外からこう書けてしまいます。
PointCard card = new PointCard();
card.setPoint(1_000_000); // いきなり100万ポイント
card.setPoint(-500); // マイナスにもできてしまう
JavaaddPoint や usePoint の中で
どれだけルールを守っていても、setPoint 一発で全部壊せます。
ここから分かるのは、
「フィールドを private にしても、
何でもありの setter を作ったら意味がない」
ということです。
PointCard の本当のカプセル化はこうです。
public class PointCard {
private int point;
public PointCard() {
this.point = 0;
}
public int getPoint() {
return point;
}
public void addPoint(int amount) {
if (amount < 0) throw new IllegalArgumentException();
this.point += amount;
}
public void usePoint(int amount) {
if (amount < 0) throw new IllegalArgumentException();
if (amount > point) throw new IllegalArgumentException();
this.point -= amount;
}
}
JavasetPoint はあえて作らない。
「ポイントを変えたいなら、必ず add / use を通れ」
というルールにする。
これが、
カプセル化を“ちゃんと使っている”状態です。
重要ポイントの深掘り:カプセル化は「操作をデザインする」こと
「値を公開するか」ではなく「どんな操作を許すか」を考える
2日目で一番大事な視点はこれです。
カプセル化とは、
「フィールドを隠すこと」ではなく、
「どんな操作だけを外に見せるかをデザインすること」。
PointCard で言えば、
「ポイントを増やす」「ポイントを使う」という
“意味のある操作” だけを公開して、
「ポイントを好きな値に書き換える」という
“危険な操作” は公開しない。
User で言えば、
「年齢を設定する」操作の中で
「0以上」というルールを守らせる。
「外から見えるのは“意味のある操作”だけ」
という状態を作るのが、
本当のカプセル化です。
もう一つの例題:銀行口座クラスで考える
直接 balance を触らせたら終わり
銀行口座を表すクラスを考えてみます。
まずはダメなやつ。
public class BankAccount {
public int balance;
}
Java外からこうできます。
BankAccount acc = new BankAccount();
acc.balance = 1_000_000; // 勝手に増やせる
acc.balance = -500; // マイナスにもできる
Javaこれでは「銀行口座」として破綻しています。
カプセル化すると、こうなります。
public class BankAccount {
private int balance;
public BankAccount(int initialBalance) {
if (initialBalance < 0) {
throw new IllegalArgumentException("初期残高は0以上");
}
this.balance = initialBalance;
}
public int getBalance() {
return balance;
}
public void deposit(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("入金額は1以上");
}
this.balance += amount;
}
public void withdraw(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("出金額は1以上");
}
if (amount > balance) {
throw new IllegalArgumentException("残高不足");
}
this.balance -= amount;
}
}
Java外からはこうしかできません。
BankAccount acc = new BankAccount(1000);
acc.deposit(500);
acc.withdraw(300);
System.out.println(acc.getBalance()); // 1200
Javaここでのポイントは、
残高を直接書き換えることはできない
「入金」「出金」という意味のある操作だけが公開されている
ルール違反(マイナス残高など)はクラス自身が防いでいる
ということです。
「直接触らせない」ことで、
クラスが自分の一貫性を守れるようになる。
これがカプセル化の強さです。
2日目で身につけてほしい“判断基準”
今日は、書き方よりも
「getter / setter をどう設計するか」 の感覚を育てる日です。
そのための判断基準を、シンプルにまとめるとこうなります。
このフィールドは、外から自由に変えられていいか?
変えられていいなら setter を作る。
変えられてほしくないなら、コンストラクタだけで設定して setter を作らない。
このフィールドは、外から読めていいか?
読めていいなら getter を作る。
読まれたくないなら getter も作らない(内部専用)。
値を変えるときに、守るべきルールはないか?
あるなら、必ず setter や専用メソッドの中でチェックする。
「とりあえず全部に getter / setter」ではなく、
「このクラスの中身をどう守りたいか」から逆算して
公開するメソッドを決める。
ここまで来たら、
あなたのカプセル化はもう
「文法を知っている」レベルではなく、
「設計として使えている」レベル に入っています。
