Java道|抽象クラスとインターフェイスで設計するクラス階層

強いクラス設計は、ただクラスを増やすことではありません。
抽象クラスで共通の土台を整え、インターフェイスで能力の約束を分けることで、和風剣士たちの関係を見通しよく整理できます。

Javaのプログラムが大きくなってくると、クラスを1つずつ個別に作るだけでは、だんだん整理が難しくなります。

最初は、WaterHashira クラス、FlameHashira クラス、MedicalSlayer クラスのように、必要なクラスを順番に作っていけば動くかもしれません。

しかし、クラスが増えてくると、次のような問題が出てきます。

困りやすいこと内容
共通部分が重複する名前や速度など、似たフィールドを何度も書いてしまう
役割が分かりにくくなるどのクラスが何を担当しているのか見えにくい
新しいクラスを追加しにくいどこに組み込めばよいか迷う
同じ処理でまとめにくいクラスごとに別々の扱いが必要になる

このようなときに役立つのが、抽象クラスとインターフェイスを組み合わせたクラス階層の設計です。

鬼滅の刃風にたとえると、和風剣士たちが増えてきたときに、全員をバラバラに考えるのではなく、

整理したいもの鬼滅の刃風のイメージ
共通の土台剣士なら名前や速度を持つ
共通の能力呼吸を使える、移動できる、情報を表示できる
個別の特徴水柱、炎柱、支援剣士としての個性

のように分けて考えるイメージです。

抽象クラスは、共通の状態や処理をまとめる土台として使います。
インターフェイスは、何ができるかという能力の約束を整理するために使います。

つまり、クラス階層を設計するとは、単に親子関係を作ることではありません。
共通部分をどこに置き、能力の約束をどこで決め、個性をどのクラスに任せるかを整理することです。

クラス階層を設計するとはどういうことか

クラス階層を設計するとは、似た役割を持つクラス同士の関係を整理することです。

オブジェクト指向では、クラスをただ横に並べるだけではなく、共通するものを上位にまとめ、違う部分を下位のクラスへ分けていきます。

鬼滅の刃風に考えると、次のような整理ができます。

分類役割
共通の土台Slayerすべての剣士に共通する名前や速度を持つ
能力の約束iMovable移動できることを表す
能力の約束iBreathUser呼吸を使えることを表す
具体クラスWaterHashira水柱としての具体的な剣士
具体クラスFlameHashira炎柱としての具体的な剣士
具体クラスMedicalSlayer支援剣士としての具体的な剣士

ここで大切なのは、「何を親クラスにするか」だけではありません。

どの情報は全剣士に共通なのか。
どの能力はクラスをまたいで使う約束なのか。
どの処理は具体クラスごとに変わるのか。

このような役割分担を考えることが、クラス階層の設計です。

↓クリックすると拡大表示されます。

抽象クラスは共通の土台を作るためのもの

抽象クラスは、複数のクラスに共通する状態や処理をまとめるために使います。

鬼滅の刃風に、Slayer という抽象クラスを考えてみます。

剣士なら、共通して次のような情報を持っていそうです。

共通要素内容
name剣士の名前
speed移動速度
setSpeed()速度を設定する処理
showBaseInfo()共通情報を表示する処理

このような「剣士なら共通して持つもの」は、抽象クラスにまとめると整理しやすくなります。

ただし、すべてを抽象クラスで完成させる必要はありません。

たとえば、show() のような情報表示メソッドは、剣士ごとに内容が変わります。

具体クラスshow() で表示したい内容
WaterHashira水柱の名前、速度、呼吸の型
FlameHashira炎柱の名前、速度、炎の技
MedicalSlayer支援剣士の名前、速度、治療技術

このように、共通の枠だけを抽象クラスに置き、具体的な表示内容は子クラスで完成させると、クラスの役割がきれいに分かれます。

抽象クラスは、共通の状態と共通の考え方をまとめる土台だと考えると分かりやすいです。

インターフェイスは能力や約束を整理するためのもの

インターフェイスは、クラスの種類ではなく、何ができるか を表すために使います。

鬼滅の刃風に考えると、和風剣士たちはそれぞれ違う存在ですが、能力としては共通点を持つことがあります。

能力インターフェイスの例意味
移動できるiMovablemove() を持つ
呼吸を使えるiBreathUseruseBreath() を持つ
情報を表示できるiDisplayableshow() を持つ
支援できるiSupportablesupport() を持つ

WaterHashira は水柱という具体クラスです。
MedicalSlayer は支援剣士という具体クラスです。

この2つは「何者か」で見ると違います。
しかし、どちらも情報を表示できるなら、iDisplayable を実装できます。

クラス何者か何ができるか
WaterHashira水柱表示できる、移動できる、呼吸を使える
MedicalSlayer支援剣士表示できる、支援できる
FlameHashira炎柱表示できる、移動できる、呼吸を使える

この「何ができるか」を共通ルールとして切り出すのがインターフェイスです。

抽象クラスが「共通の土台」を作るものなら、インターフェイスは「共通の能力の約束」を作るものです。

抽象クラスとインターフェイスを組み合わせると設計しやすい

実際の設計では、抽象クラスかインターフェイスのどちらか一方だけで考えるより、両方を組み合わせたほうが自然なことが多いです。

鬼滅の刃風に整理すると、次のようになります。

役割向いているしくみ
剣士としての共通の土台抽象クラスSlayer
移動できる約束インターフェイスiMovable
呼吸を使える約束インターフェイスiBreathUser
情報を表示できる約束インターフェイスiDisplayable
実際の剣士具体クラスWaterHashira、FlameHashira、MedicalSlayer

このように分けると、役割がはっきりします。

置き場所置く内容
抽象クラス共通の状態、共通の処理、共通の土台
インターフェイス共通の能力、共通の約束、必ず持つメソッド名
具体クラス実際の処理内容、個別の特徴、剣士らしさ

たとえば、Slayer 抽象クラスで名前や速度を持たせます。
iMovable で移動できることを表します。
iBreathUser で呼吸を使えることを表します。
WaterHashira や FlameHashira で、実際の剣士の個性を表します。

この形にすると、共通部分、能力の約束、個性が混ざりにくくなります。

↓クリックすると拡大表示されます。

抽象クラスとインターフェイスでクラスを実装する

ファイル名:Sample6.java

abstract class Slayer
{
    protected String name;
    protected int speed;

    public Slayer(String n)
    {
        name = n;
        speed = 0;
        System.out.println(name + "を剣士として登録しました。");
    }

    public void setSpeed(int s)
    {
        speed = s;
        System.out.println(name + "の速度を" + speed + "にしました。");
    }

    public void showBaseInfo()
    {
        System.out.println("剣士の名前は" + name + "です。");
        System.out.println("速度は" + speed + "です。");
    }

    public abstract void show();
}

interface iMovable
{
    void move();
}

interface iBreathUser
{
    void useBreath();
}

class WaterHashira extends Slayer implements iMovable, iBreathUser
{
    private String breathingStyle;

    public WaterHashira(String n, String bs)
    {
        super(n);
        breathingStyle = bs;
        System.out.println(name + " 水柱を作成しました。");
    }

    public void move()
    {
        System.out.println(name + "は水の流れのように素早く移動します。");
    }

    public void useBreath()
    {
        System.out.println(name + "は" + breathingStyle + "を使います。");
    }

    public void show()
    {
        showBaseInfo();
        System.out.println("役割は水柱です。");
        System.out.println("呼吸の型は" + breathingStyle + "です。");
    }
}

class MedicalSlayer extends Slayer implements iMovable
{
    private String healingSkill;

    public MedicalSlayer(String n, String hs)
    {
        super(n);
        healingSkill = hs;
        System.out.println(name + " 支援剣士を作成しました。");
    }

    public void move()
    {
        System.out.println(name + "は仲間の近くへ素早く移動します。");
    }

    public void show()
    {
        showBaseInfo();
        System.out.println("役割は支援剣士です。");
        System.out.println("治療技術は" + healingSkill + "です。");
    }
}

class Sample6
{
    public static void main(String[] args)
    {
        Slayer[] slayers = new Slayer[2];

        slayers[0] = new WaterHashira("水月", "水の呼吸");
        slayers[0].setSpeed(80);

        slayers[1] = new MedicalSlayer("藤香", "藤花治療");
        slayers[1].setSpeed(60);

        for(int i = 0; i < slayers.length; i++){
            slayers[i].show();
        }

        iMovable[] movableMembers = new iMovable[2];

        movableMembers[0] = (iMovable)slayers[0];
        movableMembers[1] = (iMovable)slayers[1];

        for(int i = 0; i < movableMembers.length; i++){
            movableMembers[i].move();
        }
    }
}

実行結果

水月を剣士として登録しました。
水月 水柱を作成しました。
水月の速度を80にしました。
藤香を剣士として登録しました。
藤香 支援剣士を作成しました。
藤香の速度を60にしました。
剣士の名前は水月です。
速度は80です。
役割は水柱です。
呼吸の型は水の呼吸です。
剣士の名前は藤香です。
速度は60です。
役割は支援剣士です。
治療技術は藤花治療です。
水月は水の流れのように素早く移動します。
藤香は仲間の近くへ素早く移動します。

Sample6.java で使っている設計

このプログラムでは、抽象クラスとインターフェイスを組み合わせています。

まず、Slayer 抽象クラスがあります。

abstract class Slayer

Slayer は、剣士としての共通の土台です。

Slayer にあるもの役割
name剣士の名前
speed剣士の速度
setSpeed()速度を設定する共通処理
showBaseInfo()名前と速度を表示する共通処理
show()子クラスで完成させる抽象メソッド

つまり、Slayer はすべての剣士に共通する情報をまとめています。

一方で、show() は abstract になっています。

public abstract void show();

これは、剣士なら自分の情報を表示できるべきだけれど、具体的な表示内容は剣士ごとに違うためです。

WaterHashira なら呼吸の型を表示します。
MedicalSlayer なら治療技術を表示します。

このように、共通の土台は抽象クラスにまとめ、個別の表示内容は具体クラスに任せています。

インターフェイスで能力を分けている

このプログラムでは、iMovable と iBreathUser という2つのインターフェイスも使っています。

interface iMovable
{
    void move();
}

iMovable は、移動できることを表す約束です。

interface iBreathUser
{
    void useBreath();
}

iBreathUser は、呼吸を使えることを表す約束です。

ここで大事なのは、どちらも「何者か」ではなく「何ができるか」を表している点です。

インターフェイス表している能力
iMovable移動できる
iBreathUser呼吸を使える

WaterHashira は、Slayer を継承しながら、iMovable と iBreathUser を実装しています。

class WaterHashira extends Slayer implements iMovable, iBreathUser

これは、WaterHashira が剣士としての共通土台を持ち、さらに移動できる能力と呼吸を使える能力を持つことを表しています。

一方、MedicalSlayer は iMovable だけを実装しています。

class MedicalSlayer extends Slayer implements iMovable

これは、MedicalSlayer が剣士としての共通土台を持ち、移動できる能力も持つことを表しています。

クラス抽象クラスの土台実装する能力
WaterHashiraSlayeriMovable、iBreathUser
MedicalSlayerSlayeriMovable

このように、抽象クラスで土台を作り、インターフェイスで能力を追加する形にすると、設計がかなり見やすくなります。

多くのクラスをまとめて扱えることが大きな利点

抽象クラスやインターフェイスを使う大きな利点は、複数のクラスを共通の型でまとめて扱えることです。

Sample6.java では、Slayer[] 配列に WaterHashira と MedicalSlayer を入れています。

Slayer[] slayers = new Slayer[2];

slayers[0] = new WaterHashira("水月", "水の呼吸");
slayers[1] = new MedicalSlayer("藤香", "藤花治療");

WaterHashira も MedicalSlayer も Slayer を継承しているため、Slayer[] にまとめられます。

配列要素変数としての型実体
slayers[0]SlayerWaterHashira
slayers[1]SlayerMedicalSlayer

そして、次のように同じ show() を呼び出せます。

for(int i = 0; i < slayers.length; i++){
    slayers[i].show();
}

呼び出し方は同じです。
しかし、実際に動く show() は、実体ごとに変わります。

実体動く show()
WaterHashira水柱としての情報を表示する
MedicalSlayer支援剣士としての情報を表示する

これが、抽象クラスを使った多態性です。

さらに、iMovable[] 配列も使っています。

iMovable[] movableMembers = new iMovable[2];

movableMembers[0] = (iMovable)slayers[0];
movableMembers[1] = (iMovable)slayers[1];

iMovable を実装しているクラスなら、クラスの種類が違っていても同じ配列に入れられます。

配列要素実体共通して使えるメソッド
movableMembers[0]WaterHashiramove()
movableMembers[1]MedicalSlayermove()

このように、抽象クラスでは「剣士としてまとめる」、インターフェイスでは「移動できる者としてまとめる」という使い方ができます。

新しいクラスの追加や差し替えがしやすくなる

クラス階層をきちんと設計しておくと、新しいクラスを追加しやすくなります。

たとえば、あとから FlameHashira クラスを追加したいとします。

FlameHashira が Slayer を継承し、iMovable と iBreathUser を実装するなら、既存の設計に自然に組み込めます。

新しいクラスを追加する

ファイル名:Sample7.java

class FlameHashira extends Slayer implements iMovable, iBreathUser
{
    private String flameTechnique;

    public FlameHashira(String n, String ft)
    {
        super(n);
        flameTechnique = ft;
        System.out.println(name + " 炎柱を作成しました。");
    }

    public void move()
    {
        System.out.println(name + "は炎の勢いで前線へ踏み込みます。");
    }

    public void useBreath()
    {
        System.out.println(name + "は" + flameTechnique + "を放ちます。");
    }

    public void show()
    {
        showBaseInfo();
        System.out.println("役割は炎柱です。");
        System.out.println("炎の技は" + flameTechnique + "です。");
    }
}

この FlameHashira は、既存の設計にそのまま乗せられます。

追加クラス継承する抽象クラス実装するインターフェイス
FlameHashiraSlayeriMovable、iBreathUser

このようにしておけば、使う側のコードは「このクラス名は何か」よりも、「Slayer として扱えるか」「iMovable として扱えるか」を見ればよくなります。

つまり、クラス階層を整えておくと、追加や差し替えがしやすくなります。

インターフェイスは多重継承の考え方も支える

Javaでは、クラスの多重継承はできません。

つまり、1つのクラスが複数の親クラスを同時に継承することはできません。

// これはできない
class WaterHashira extends Slayer, BreathMaster
{
}

しかし、インターフェイスなら複数実装できます。

class WaterHashira extends Slayer implements iMovable, iBreathUser

これは、WaterHashira が Slayer という1つの土台を持ちながら、iMovable と iBreathUser という複数の能力の約束を持つことを表しています。

しくみJavaでの扱い
クラスの多重継承できない
インターフェイスの複数実装できる
共通の土台抽象クラスで表す
複数の能力インターフェイスで表す

鬼滅の刃風にたとえると、1人の剣士が複数の血筋や所属を同時に親として持つのではありません。
その代わりに、「移動できる」「呼吸を使える」「支援できる」といった複数の能力札を持つイメージです。

この考え方によって、クラス構造をシンプルに保ちながら、能力は柔軟に追加できます。

標準クラスライブラリを見るときにも階層を意識する

Javaの標準クラスライブラリにも、クラス階層があります。

自分でクラスを作るときだけでなく、標準クラスを使うときにも、

確認したいこと意味
どのクラスを継承しているかどんな土台を持つか
どのインターフェイスを実装しているかどんな能力や約束を持つか
どのメソッドをオーバーライドしているかどんな振る舞いを自分用に変えているか

を見ていくと、そのクラスの役割が分かりやすくなります。

鬼滅の刃風にたとえると、新しい剣士が登場したときに、どの流派の系譜に属しているのか、どんな能力札を持っているのかを確認するようなものです。

この見方ができるようになると、自分でクラスを設計するときにもかなり役立ちます。

図:抽象クラスとインターフェイスで作るクラス階層

↓クリックすると拡大表示されます。

この図が示していること

この図では、抽象クラス、インターフェイス、具体クラスの役割分担を表しています。

上部中央の Slayer 抽象クラスは、剣士としての共通の土台です。
name や speed のような共通状態、setSpeed() や showBaseInfo() のような共通処理を持っています。

左上の iMovable は、移動できるという能力の約束です。
右上の iBreathUser は、呼吸を使えるという能力の約束です。

下部の WaterHashira、FlameHashira、MedicalSlayer は具体クラスです。
それぞれ Slayer を継承し、必要なインターフェイスを実装しています。

この図から、次の役割分担が分かります。

要素役割
抽象クラス共通の土台をまとめる
インターフェイス能力の約束を整理する
具体クラス実際の個性と処理を表す

図:共通の型でまとめて扱う設計

↓クリックすると拡大表示されます。

この図が示していること

この図では、抽象クラス型とインターフェイス型で、異なる具体クラスをまとめて扱う様子を表しています。

左側の Slayer[] slayers では、WaterHashira、FlameHashira、MedicalSlayer を剣士としてまとめています。
そのため、共通して show() を呼び出せます。

右側の iMovable[] movableMembers では、移動できるクラスをまとめています。
そのため、共通して move() を呼び出せます。

まとめ方呼び出せるメソッド見方
剣士としてまとめるSlayer[]show()共通の土台で見る
移動できる者としてまとめるiMovable[]move()共通の能力で見る

この図から分かることは、抽象クラス型では「何者か」でまとめ、インターフェイス型では「何ができるか」でまとめられるということです。

鬼滅の刃風にクラス階層の設計を整理する

鬼滅の刃風に考えると、クラス階層の設計は、和風剣士たちの世界を整理する作業です。

剣士たちは、それぞれ違う個性を持っています。

具体クラス個性
WaterHashira水の呼吸を使う
FlameHashira炎の技を使う
MedicalSlayer仲間を治療する

しかし、全員に共通する土台もあります。

共通の土台内容
name名前
speed速度
showBaseInfo()基本情報の表示

さらに、クラスをまたいで共通する能力もあります。

能力の約束内容
iMovable移動できる
iBreathUser呼吸を使える
iSupportable支援できる

このように整理すると、抽象クラスとインターフェイスの役割がはっきりします。

設計要素鬼滅の刃風のイメージ
抽象クラス剣士としての共通の土台
インターフェイス能力の約束
具体クラス実際の和風剣士
クラス階層共通ルール、能力、個性を整理した構造

抽象クラスとインターフェイスを使い分けることで、クラスが増えても整理された構造を保ちやすくなります。

この内容で押さえておきたいポイント

ポイント内容
クラス階層の設計共通部分、能力、個性をどこに置くか整理すること
抽象クラス共通の状態や処理をまとめる土台
インターフェイス何ができるかという能力の約束
具体クラス実際の処理内容と個別の特徴を表す
抽象クラス型複数の具体クラスを共通の土台でまとめられる
インターフェイス型複数の具体クラスを共通の能力でまとめられる
追加しやすさ新しいクラスを既存の階層に組み込みやすくなる
多重継承の考え方クラスの親は1つ、インターフェイスは複数実装できる

抽象クラスとインターフェイスは、どちらも大きなプログラムを整理するための設計道具です。

抽象クラスは、剣士としての共通の土台を作ります。
インターフェイスは、移動できる、呼吸を使える、支援できるといった能力の約束を作ります。
具体クラスは、それらを組み合わせて、それぞれの和風剣士らしさを表します。

この役割分担が見えると、クラス階層はただの親子関係ではなく、プログラム全体を見通しよく組み立てるための設計図として理解できるようになります。