Java | 1 日 120 分 × 7 日アプリ学習 中級編:オブジェクト指向(OOP) - カプセル化アプリ

Web APP Java
スポンサーリンク

2日目のゴール

2日目のテーマも
「直接触らせない」=カプセル化

1日目は
privategetter / setter の「基本の形」と
「なぜ直接触らせないのか」という感覚をつかみました。

2日目はそこから一歩進んで、
「どこまで隠すか」「どこまで見せるか」
意識して設計できるようになることを目指します。


もう一度整理:なぜ“直接触らせない”のか

「値を守る」だけじゃなく「ルールを守る」ため

1日目の User クラスを思い出してください。

public class User {
    public String name;
    public int age;
}
Java

これは「誰でも、どこからでも、どんな値でも入れられる」状態でした。

age = -10
name = 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);       // マイナスにもできてしまう
Java

addPointusePoint の中で
どれだけルールを守っていても、
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;
    }
}
Java

setPoint はあえて作らない。
「ポイントを変えたいなら、必ず 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」ではなく、
「このクラスの中身をどう守りたいか」から逆算して
公開するメソッドを決める

ここまで来たら、
あなたのカプセル化はもう
「文法を知っている」レベルではなく、
「設計として使えている」レベル に入っています。

タイトルとURLをコピーしました