この回では、オブジェクト指向を「現実の物を何でもクラスにすること」ではなく、状態の整合性を守る方法として学びます。前提は第04回です。
不正な状態を作らせない
残高を公開変数にすると、どの場所からでも負の数へ書き換えられます。privateにして、入金という操作だけを公開すれば、検査を一か所に集められます。これがカプセル化の実用的な価値です。
public class Main {
static final class Wallet {
private int yen;
Wallet(int initialYen) {
if (initialYen < 0) {
throw new IllegalArgumentException("初期残高が負です");
}
this.yen = initialYen;
}
void deposit(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("入金額は正の数です");
}
yen = Math.addExact(yen, amount);
}
int balance() {
return yen;
}
}
public static void main(String[] args) {
Wallet travel = new Wallet(1000);
Wallet food = new Wallet(500);
travel.deposit(200);
System.out.println(travel.balance());
System.out.println(food.balance());
}
}
Main.javaの出力は1200と500です。newで作った二つのインスタンスは、それぞれの残高を持ちます。コンストラクタは生成時に実行され、thisは現在のインスタンスを指します。staticなフィールドを残高に使うと、個々の財布ではなくクラスで共有する値になってしまいます。
メソッドは業務上の操作を表す
すべてのフィールドにsetメソッドを作るだけでは、自由な書き換えと大差ありません。depositのように目的を表す名前を使うと、不変条件を保ちやすくなります。不変条件とは「残高が負ではない」など、操作の前後で常に守りたい性質です。
一方、名前と金額を運ぶだけの値にはrecordも便利です。recordの各構成要素はfinalですが、中に可変リストを持てば、そのリストまで自動的に変更不能になるわけではありません。深い不変性は第15回で扱います。
練習と解答
練習:出金メソッドを追加してください。0以下の額と残高を超える額を拒否し、失敗したときは残高を変えない仕様にします。
解答例:if (amount <= 0 || amount > yen) throw new IllegalArgumentException("出金できません"); の後で yen -= amount; とします。先に引いてからエラーにすると、失敗後の状態が壊れます。検査、計算、更新の順番までが設計です。残高と同額の出金は成功し、0になることも確認します。
公式資料
クラスとオブジェクトを参照できます。
Java全20回の目次 | 前の回 | 次の回