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

Web APP Java
スポンサーリンク

4日目のゴール

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

1〜3日目であなたはすでに

フィールドを private にする
必要なものだけ getter / setter を出す
クラスの「壊れてはいけない約束(不変条件)」を中に閉じ込める

というところまで来ています。

4日目はここから一歩進んで、
「カプセル化と“外の世界”との付き合い方」 を扱います。

つまり、
「他のクラスからどう見えるか」まで含めて
privategetter / setter を設計する
日です。


カプセル化は「自分だけの話」では終わらない

クラスは必ず“他人”と一緒に使われる

カプセル化は、
「このクラスの中身を守る」ための仕組みですが、
そのクラスは必ず 他のクラスと一緒に使われます。

ユーザー情報を持つ User は、
画面表示クラスやサービスクラスから使われる。

ポイントカード PointCard は、
決済処理クラスから使われる。

銀行口座 BankAccount は、
振込処理クラスから使われる。

4日目の視点はこうです。

「他のクラスから見たとき、
このクラスは“どう見えていてほしいか”?」

この視点で、
privategetter / 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 を
「残高という整数を持つ入れ物」として見せたいのか、
「入金・出金という操作を提供する口座」として見せたいのか。

後者を選ぶと、
自然とこうなります。

フィールドは private
getter / 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日目以降は
「カプセル化 × コレクション」「カプセル化 × 複数クラスの連携」
みたいな、さらに一段複雑な世界にも
自然に入っていけます。

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