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

Web APP Java
スポンサーリンク
スポンサーリンク

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

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

フィールドは 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をコピーしました