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

Web APP Java
スポンサーリンク

4日目のゴール

継承アプリ4日目のテーマも
「共通部分をまとめる」。

1日目:フィールドや単純なメソッドの共通化。
2日目:どこまで共通にして、どこから別物にするかの線引き。
3日目:処理の“流れ”そのものを共通化(テンプレート的な継承)。

4日目はここから一歩進んで、
「共通部分をまとめた結果、“使う側”のコードがどう変わるか」
にフォーカスします。

つまり、
継承で共通化したクラスを「使う側の目線」で見直して、

同じメソッドで扱える
型を意識せずに呼び出せる

という “気持ちよさ” を、ちゃんと言葉にして感じてもらう日です。


共通部分をまとめると、“使う側”が楽になる

「Employee 用メソッド」「PartTimer 用メソッド」が増えていく世界

まず、継承を使わなかった場合の
“使う側のしんどさ”を見てみます。

社員とアルバイトがいて、
それぞれにクラスがあるとします(継承なし)。

public class Employee {
    private String name;
    private int age;
    private int monthlySalary;

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

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

    public int calculateMonthlyPay() {
        return monthlySalary;
    }
}
Java
public class PartTimer {
    private String name;
    private int age;
    private int hourlyWage;
    private int workingHours;

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

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

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

ここまではいいとして、
「社員とアルバイトをまとめて処理したい」場面を考えます。

例えば、
「全員の自己紹介を表示する」メソッド。

public class HumanResourceService {

    public void introduceAll(List<Employee> employees, List<PartTimer> partTimers) {
        for (Employee e : employees) {
            e.introduce();
        }
        for (PartTimer p : partTimers) {
            p.introduce();
        }
    }
}
Java

社員用のループ
アルバイト用のループ

似たようなコードが2回出てきます。

「給与の合計を出す」メソッドも同じです。

public int sumMonthlyPay(List<Employee> employees, List<PartTimer> partTimers) {
    int sum = 0;
    for (Employee e : employees) {
        sum += e.calculateMonthlyPay();
    }
    for (PartTimer p : partTimers) {
        sum += p.calculateMonthlyPay();
    }
    return sum;
}
Java

「やっていることは同じなのに、型が違うせいで2回書かされている」
これが、継承なしの世界で“使う側”が抱えるしんどさです。


共通部分をまとめると、“1本のループ”で書ける

Person を親にして、共通の型で扱う

ここで、1日目・2日目でやったように
Person を親クラスとして導入します。

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();

    public abstract int calculateMonthlyPay();
}
Java

Employee はこう。

public class Employee extends Person {
    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 {
    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

ここでのポイントは、

introduce()calculateMonthlyPay()
「Person としての共通インターフェース」にしたことです。

使う側のコードは、こう変わります。

public class HumanResourceService {

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

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

社員もアルバイトも、
「Person として」一つのリストに入れられる。

ループも1本で済む。
メソッドも1種類で済む。

ここで初めて、
「共通部分をまとめる」が“使う側の楽さ”として効いてくる
のを実感できます。


重要ポイントの深掘り1:「共通部分をまとめる」と「共通の“顔”で扱える」

継承は“型を揃える”ための道具でもある

4日目で強く意識してほしいのは、
継承の効果が

クラスの中の重複が減る
だけではなく、

「使う側から見たときに、同じ“顔”で扱えるようになる」
というところにある、ということです。

Person を親にしたことで、

Employee も PartTimer も
introduce()calculateMonthlyPay() を持つ存在になった。

だから、
List<Person> という一つのコレクションにまとめられる。

これは、
「共通部分をまとめる」=「共通のインターフェースを持たせる」
ということでもあります。

継承は、
「中身の共通化」と同時に
「外から見たときの共通化」もしている。

ここに気づくと、
継承の価値が一段クリアになります。


例題:通知クラスを“使う側”から見てみる

MailNotification と PushNotification を一緒に扱いたい

前にやった通知の例を、
今度は「使う側」から見てみます。

継承を使ったバージョンを思い出します。

public abstract class Notification {
    protected String body;

    public Notification(String body) {
        this.body = body;
    }

    public abstract void send();
}
Java
public class MailNotification extends Notification {
    private String toAddress;
    private String subject;

    public MailNotification(String toAddress, String subject, String body) {
        super(body);
        this.toAddress = toAddress;
        this.subject = subject;
    }

    @Override
    public void send() {
        System.out.println("メール送信: " + toAddress + " / " + subject);
        System.out.println("本文: " + body);
    }
}
Java
public class PushNotification extends Notification {
    private String userId;
    private String title;

    public PushNotification(String userId, String title, String body) {
        super(body);
        this.userId = userId;
        this.title = title;
    }

    @Override
    public void send() {
        System.out.println("プッシュ送信: " + userId + " / " + title);
        System.out.println("本文: " + body);
    }
}
Java

使う側のコードは、こう書けます。

public class NotificationService {

    public void sendAll(List<Notification> notifications) {
        for (Notification n : notifications) {
            n.send();
        }
    }
}
Java

メールだろうがプッシュだろうが、
「Notification として」一つのリストに入れて、
send() を呼ぶだけ。

もし継承がなかったら、
こうなります。

public void sendAll(List<MailNotification> mails, List<PushNotification> pushes) {
    for (MailNotification m : mails) {
        m.send();
    }
    for (PushNotification p : pushes) {
        p.send();
    }
}
Java

型ごとにメソッド引数が増え、
ループも増え、
似たコードが増えていく。

4日目で感じてほしいのは、
「共通部分をまとめる」とは、
“使う側のコードをシンプルにするための投資”でもある

ということです。


重要ポイントの深掘り2:「共通の型で扱える」とテストも楽になる

テストコードの重複も減る

継承で共通部分をまとめると、
テストコードにも良い影響が出ます。

例えば、Person の例。

sumMonthlyPay をテストしたいとき、
List<Person> に Employee と PartTimer を混ぜて入れて、
一発でテストできます。

@Test
void testSumMonthlyPay() {
    List<Person> people = new ArrayList<>();
    people.add(new Employee("A", 30, 300000));
    people.add(new PartTimer("B", 20, 1000, 80)); // 8万円

    HumanResourceService service = new HumanResourceService();
    int sum = service.sumMonthlyPay(people);

    assertEquals(380000, sum);
}
Java

もし継承がなくて、
Employee と PartTimer がバラバラの型だったら、

Employee 用のテスト
PartTimer 用のテスト

を別々に書く必要が出てきます。

「共通の型で扱える」=「共通のテストが書ける」
というのも、
継承で共通部分をまとめる大きなメリットです。


4日目の実践:「使う側のコード」を先にイメージしてみる

先に「こう使いたい」を書いてから、継承を考える

今日やってみてほしい練習は、
いつもと順番を逆にすることです。

クラスを設計するときに、
いきなりクラスから書き始めるのではなく、

「使う側のコード」を先にイメージしてみる。

例えば、こうです。

「社員もアルバイトも、
List<Person> に入れて introduceAll したい」

「メール通知もプッシュ通知も、
List<Notification> に入れて sendAll したい」

「PDF レポートも CSV レポートも、
List<Report> に入れて exportAll したい」

この「こう使いたい」という形が見えたら、
それに合わせて

共通の親クラスは何か?
共通のメソッド名は何か?

を決めていく。

「共通部分をまとめる」ことと
「共通の使い方を提供する」ことを
セットで考える
のが、4日目のテーマです。


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

継承アプリ4日目で伝えたいのは、
「共通部分をまとめる」とは、
“クラスの中身”だけの話ではなく、
“使う側のコード”まで含めた設計だ

ということです。

共通の親クラスを持たせることで、
共通のメソッド名を持たせる。

共通のメソッド名を持たせることで、
共通の型(List<Person>, List<Notification>)で扱える。

共通の型で扱えることで、
ループが1本になり、テストもシンプルになる。

ここまで見えてくると、
継承は単なる「コードの再利用」ではなく、
「コードの形と使い方をデザインする道具」
として感じられるようになります。

この感覚を持ったまま、
5日目以降は
「継承」と「インターフェース」「ポリモーフィズム」が
どうつながっていくかを、
さらにクリアにしていけます。

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