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

Web APP Java
スポンサーリンク

7日目のゴール

継承アプリ7日目のテーマも「共通部分をまとめる」。
でも今日は、ここまで6日間やってきたことを

「なんとなく分かる」から
「自分の言葉で説明できる」

まで持ち上げる日です。

そしてもう一歩だけ踏み込んで、
「継承でまとめる」と「インターフェースで揃える」の違い にも触れます。
ここを押さえると、継承が“怖い道具”ではなく“選べる道具”になります。


ここまでの「継承で共通部分をまとめる」を言語化する

データの共通部分をまとめる

最初にやったのは、
「似たクラスのフィールドの共通部分」を親に出すことでした。

Person に「名前」「年齢」をまとめる。
Employee / PartTimer は「雇用形態としての違い」だけを持つ。

ここでのポイントは、
共通にしたのは「フィールド名」ではなく「意味」だったことです。

「人としての情報」は共通。
「給料の計算方法」は違う。

だから、前者だけを親に出し、
後者は子に残した。

この感覚を言葉にすると、こうなります。

自分は、継承で共通化するとき、
「同じ世界・同じ意味のもの」だけを親にまとめる。
たまたま名前が同じなだけのものは、親にしない。

これが、データの共通化の軸です。


振る舞い(手順)の共通部分をまとめる

次にやったのは、
「処理の流れそのもの」を親にまとめることでした。

Logger の log()
Report の export()

どちらも、

共通の流れ(テンプレート)は親が持つ
中の一部だけを子に任せる

という形でした。

Logger なら、

レベルとメッセージを受け取る
時刻を付ける
フォーマットする
どこかに書く(ここだけ子に任せる)

Report なら、

データを集める
ヘッダーを書く
本文を書く
フッターを書く
保存する

という流れを親が握り、
各ステップの中身は子が実装する。

これを言葉にすると、こうなります。

自分は、複数のクラスで「同じ手順」を繰り返しているとき、
その手順を親クラスにテンプレートとしてまとめる。
その中の「ここだけ違う」部分を、abstract メソッドとして子に任せる。

これが、振る舞いの共通化の軸です。


使う側から見た「共通の顔」を揃える

4日目で強く出てきたのが、
「共通部分をまとめると、使う側が楽になる」という視点でした。

Person を親にしたことで、
Employee も PartTimer も Person として扱えるようになった。

List<Person> にまとめて入れて、
introduceAllsumMonthlyPay を一発で書けるようになった。

Notification を親にしたことで、
メールもプッシュも SMS も Notification として扱えるようになった。

List<Notification> に入れて、
sendAll で一気に送れるようになった。

これを言葉にすると、こうなります。

自分は、継承で共通部分をまとめるとき、
「使う側が同じメソッドで扱えるか」を意識する。
共通の親型(Person, Notification, Report)を用意して、
そこに共通のメソッド名を定義することで、
ループやテストをシンプルにする。

ここまで来ると、
継承は「中身の再利用」だけでなく
「外から見たときの共通化」の道具になっています。


長持ちする継承にするための条件

6日目でやったのは、
「共通部分をまとめたあとに壊れない継承とは何か」でした。

Notification に SMS を足したとき、
親を触らずに済んだか?

Report に Excel を足したとき、
親を触らずに済んだか?

ここから導けるルールは、とてもシンプルです。

自分は、親クラスに入れるのは
「今いる子クラス」だけでなく
「将来増えそうな子クラス」にも
自然に当てはまるものだけにする。
一部の子だけが必要な事情は、その子の中に閉じ込める。
新しい子を追加するとき、親を触らずに済むかをチェックする。

これが、
「長持ちする継承」を見分ける自分なりの基準になります。


継承とインターフェースの違いを、共通化の視点から見る

「中身も共有したい」のか「顔だけ揃えたい」のか

ここで、7日目らしく
継承とインターフェースの違いを
「共通部分をまとめる」という視点から整理してみます。

継承(extends)は、

フィールドも
メソッドの実装も
親の設計方針も

丸ごと引き継ぐ仕組みでした。

だから、

中身(実装)も再利用したい
処理の流れも共通にしたい

ときに向いています。

一方、インターフェースは、

メソッドの「形」だけを決める
中身の実装は一切持たない

という仕組みです。

例えば、こんなインターフェース。

public interface Payable {
    int calculateMonthlyPay();
}
Java

Employee も PartTimer も、
Payable を実装できます。

public class Employee implements Payable {
    @Override
    public int calculateMonthlyPay() {
        return monthlySalary;
    }
}
Java
public class PartTimer implements Payable {
    @Override
    public int calculateMonthlyPay() {
        return hourlyWage * workingHours;
    }
}
Java

使う側は、こう書けます。

public int sumMonthlyPay(List<Payable> payables) {
    int sum = 0;
    for (Payable p : payables) {
        sum += p.calculateMonthlyPay();
    }
    return sum;
}
Java

ここで起きていることは、

共通化しているのは「メソッド名と引数・戻り値」だけ
中身の実装は、各クラスが自由に持っている

という状態です。

これを言葉にすると、こうなります。

自分は、
「中身の実装も共有したい」ときは継承を使う。
「共通のメソッド名で扱いたいだけ」のときは、
インターフェースで“顔だけ揃える”ことも選択肢に入れる。

継承は「共通部分をまとめる+実装を共有する」。
インターフェースは「共通の顔だけを揃える」。

どちらも「共通化」の道具ですが、
効いているレイヤーが違う、ということです。


例題:Person 継承と Payable インターフェースを組み合わせる

「人としての共通部分」と「給料計算としての共通部分」

ここまでの話を一つにまとめる例を出します。

Person 継承で「人としての共通部分」をまとめつつ、
Payable インターフェースで「給料計算としての共通部分」を揃える、
という構成です。

public abstract class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        if (age < 0) {
            throw new IllegalArgumentException("年齢は0以上");
        }
        this.name = name;
        this.age = age;
    }

    public abstract void introduce();
}
Java
public interface Payable {
    int calculateMonthlyPay();
}
Java

Employee はこう。

public class Employee extends Person implements Payable {
    private int monthlySalary;

    public Employee(String name, int age, int monthlySalary) {
        super(name, age);
        this.monthlySalary = monthlySalary;
    }

    @Override
    public void introduce() {
        System.out.println("社員: " + name + " (" + age + "歳)");
    }

    @Override
    public int calculateMonthlyPay() {
        return monthlySalary;
    }
}
Java

PartTimer も同様。

public class PartTimer extends Person implements Payable {
    private int hourlyWage;
    private int workingHours;

    public PartTimer(String name, int age, int hourlyWage, int workingHours) {
        super(name, age);
        this.hourlyWage = hourlyWage;
        this.workingHours = workingHours;
    }

    @Override
    public void introduce() {
        System.out.println("アルバイト: " + name + " (" + age + "歳)");
    }

    @Override
    public int calculateMonthlyPay() {
        return hourlyWage * workingHours;
    }
}
Java

使う側は、こう書き分けられます。

人として紹介したいとき:

public void introduceAll(List<Person> people) {
    for (Person p : people) {
        p.introduce();
    }
}
Java

給料の合計を出したいとき:

public int sumMonthlyPay(List<Payable> payables) {
    int sum = 0;
    for (Payable p : payables) {
        sum += p.calculateMonthlyPay();
    }
    return sum;
}
Java

ここでの設計を言葉にすると、こうです。

Person 継承で「人としての共通部分」をまとめる。
Payable インターフェースで「給料計算としての共通部分」を揃える。
継承は“中身の共有”、インターフェースは“顔の共有”として使い分ける。

これが、
「共通部分をまとめる」を継承とインターフェースの両方で
立体的に捉えられている状態です。


7日目で本当に掴んでほしいこと

7日間の「継承アプリ」で、あなたはもう

フィールドやメソッドの共通部分を親にまとめる感覚
処理の流れをテンプレートとして親にまとめる感覚
使う側から見た「共通の顔」を揃える感覚
長持ちする継承と、苦しくなる継承の違い
継承とインターフェースを“共通化の道具”として選び分ける感覚

を、全部一度は通っています。

これを自分の言葉でまとめるなら、こんな感じです。

自分は、共通部分をまとめるとき、
まず「何が同じ意味で共通しているか」を言葉で整理する。
「〜である」関係なら継承で中身ごとまとめる。
「〜として扱える」関係ならインターフェースで顔だけ揃える。
親に入れるのは「全員に本当に共通すること」だけにして、
新しい子を追加するときに親を触らずに済むかを常に意識する。

この軸を持っている時点で、
継承はもう「怖い機能」ではなく、
あなたが選んで使える設計の道具 になっています。

あとは、この感覚を
自分のアプリ(家計簿、ゲーム、日記、タスク管理…)に
どんどん持ち込んでみてください。

「これ、共通部分をまとめたら“使う側”が楽になるかな?」
と問いかけながら継承やインターフェースを選べるあなたは、
もう立派に“中級”のその先に足を踏み入れています。

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