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;
}
}
Javapublic 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();
}
JavaEmployee はこう。
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;
}
}
JavaPartTimer はこう。
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();
}
Javapublic 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);
}
}
Javapublic 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日目以降は
「継承」と「インターフェース」「ポリモーフィズム」が
どうつながっていくかを、
さらにクリアにしていけます。


