見出し画像

3分間プログラミング講座 第9回: 設計の約束事~インターフェースと抽象クラス~



登場人物:

  • チャット先生(通称:先生): オブジェクト指向マスター。普段は優しく、たまにユーモアも交えながら解説。

  • ボット助手(通称:ボット君): プログラミングを始めたばかりの若手。素朴な疑問を投げかけるのが得意。


ボット君: 先生!前回はオブジェクト同士の連携について学びました!クラスがチームで働くって、なんだかシステムが生き物みたいで面白いです!

先生: ハハハ、ボット君!その感覚、とても大切だよ。オブジェクト指向では、クラスやオブジェクトがそれぞれ役割を持ち、連携し合うことで、複雑な機能を柔軟に実現するんだ。今日は、その「連携」をさらに強力にするための「設計の約束事」について学んでいこう。それが「インターフェース」と「抽象クラス」だ。

ボット君: 約束事ですか?なんだか難しそうです…!

先生: 大丈夫、心配いらないよ。これらは、プログラムの「設計図」をより明確にし、将来の変更に強くするための、とても便利なツールなんだ。特に、複数のクラスに共通の振る舞いを強制したり、共通の基盤を提供したりするのに役立つんだ。


インターフェースとは?~「契約」を定義する~

先生: まずは「インターフェース(Interface)」から説明しよう。インターフェースは、簡単に言うと「クラスが持たなければならない機能のリスト」や「クラスが守るべき契約」のようなものだ。

ボット君: 契約…?

先生: そう。例えば、家電製品を考えてみてごらん。「電源を入れる」「電源を切る」という機能は、テレビにも、冷蔵庫にも、洗濯機にも共通してあるよね?

ボット君: はい、あります!

先生: これらの家電製品は、それぞれ違う種類の機械だけど、「電源のオンオフ」という共通の「操作方法」を持っている。この「操作方法」だけを定めたものがインターフェースのイメージに近い。インターフェースは「何をすべきか」だけを定義し、「どのようにすべきか」は定義しないんだ。

ボット君: 「何をすべきか」だけ…?具体的にはどういうことですか?

先生: インターフェースには、メソッドの「宣言」だけが書かれている。つまり、メソッドの名前、引数、戻り値の型だけが定義されていて、具体的な処理内容(実装)は書かれていないんだ。そのインターフェースを「実装(implement)」するクラスが、具体的な処理内容を記述する義務を負う。

先生: なお、Java 8以降では、インターフェースにも**デフォルトメソッド(defaultキーワード)やスタティックメソッド(staticキーワード)**といった、具体的な実装を持つメソッドを記述できるようになったんだ。これにより、既存のインターフェースに新しい機能を追加する際の後方互換性の問題を解決できるようになったんだ。

Java

// インターフェースの例: 電源操作
interface PowerControl {
    // 抽象メソッド(実装はしない)
    void turnOn();
    void turnOff();

    // Java 8以降:デフォルトメソッド(実装を持つことができる)
    // 既存の実装クラスに影響を与えずに、新しい機能を追加できる
    default void reset() {
        turnOff();
        turnOn();
        System.out.println("(共通処理)リセットしました。");
    }

    // Java 8以降:スタティックメソッド
    // インターフェース自体に関連するユーティリティ機能など
    static void showManual() {
        System.out.println("電源操作マニュアルを表示");
    }
}

// インターフェースを実装するクラス: テレビ
class Television implements PowerControl {
    @Override
    public void turnOn() {
        System.out.println("テレビの電源が入りました。");
    }

    @Override
    public void turnOff() {
        System.out.println("テレビの電源が切れました。");
    }
    // reset()メソッドは実装しなくてもデフォルト実装が使われる
}

// インターフェースを実装するクラス: 冷蔵庫
class Refrigerator implements PowerControl {
    @Override
    public void turnOn() {
        System.out.println("冷蔵庫の電源が入り、冷却を開始しました。");
    }

    @Override
    public void turnOff() {
        System.out.println("冷蔵庫の電源が切れました。");
    }
    // reset()メソッドは実装しなくてもデフォルト実装が使われる
}

public class InterfaceExample {
    public static void main(String[] args) {
        PowerControl tv = new Television();
        PowerControl fridge = new Refrigerator();

        System.out.println("家電の電源を入れます。");
        tv.turnOn();
        fridge.turnOn();

        System.out.println("\n家電の電源をリセットします。");
        tv.reset(); // デフォルトメソッドの呼び出し
        fridge.reset();

        System.out.println("\nマニュアルを表示します。");
        PowerControl.showManual(); // スタティックメソッドの呼び出し(インスタンスではなくインターフェース名から呼ぶ)

        System.out.println("\n家電の電源を切ります。");
        tv.turnOff();
        fridge.turnOff();
    }
}

先生: この例では、TelevisionもRefrigeratorもPowerControlインターフェースをimplements(実装)している。これにより、どちらのクラスもturnOn()とturnOff()メソッドを持つことが保証されるんだ。そして、PowerControl型の変数で、異なる種類の家電を同じように操作できる。これはポリモーフィズムの強力な応用例でもあるね。さらに、デフォルトメソッドで共通処理を提供しつつ、必要に応じて各クラスでオーバーライドもできるんだ。

ボット君: なるほど!「この機能は必ず持ってくださいね」っていう約束事なんですね!だから、テレビでも冷蔵庫でも同じようにturnOn()って呼べるんだ!デフォルトメソッドやスタティックメソッドもあるなんて、インターフェースって進化してるんですね!


抽象クラスとは?~「共通の基盤」を提供する~

先生: 次に「抽象クラス(Abstract Class)」だ。抽象クラスは、簡単に言うと「不完全なクラス」または「共通の基盤を提供するクラス」のようなものだ。

ボット君: 不完全…?どういうことですか?

先生: 抽象クラスは、通常のクラスと同じように属性や完全に実装されたメソッドを持つことができる。しかし、一つ以上の「抽象メソッド(Abstract Method)」を持つことができるんだ。抽象メソッドは、インターフェースのメソッドと同じく、具体的な処理内容を持たないメソッドのことだよ。

先生: 抽象クラスは、それ自体を直接オブジェクトとして生成することはできない。必ず、その抽象クラスを「継承(extends)」した子クラスが、抽象メソッドをすべて実装する必要があるんだ。

ボット君: 継承するんですね!インターフェースはimplementsで、抽象クラスはextends…?

先生: その通り!それが大きな違いの一つだね。例えば、「乗り物」を考えてみよう。

  • 全ての乗り物には「移動する」という共通の機能がある。

  • しかし、「移動する」方法は、車と自転車では全く違う。

  • 「車」という乗り物には「エンジン」がある、といった共通の属性もある。

先生: この場合、「乗り物」を抽象クラスとして定義し、「移動する」メソッドを抽象メソッドにする。そして、車や自転車といった具体的な乗り物のクラスが、この抽象クラスを継承して、それぞれの移動方法を実装するんだ。

Java

// 抽象クラスの例: 乗り物
abstract class Vehicle {
    protected String name; // 共通の属性(フィールド)

    public Vehicle(String name) { // コンストラクタも持てる
        this.name = name;
    }

    // 抽象メソッド: 各乗り物で具体的な移動方法を実装させる
    public abstract void move();

    // 完全に実装されたメソッド: 全ての乗り物に共通の動作
    public void displayInfo() {
        System.out.println("乗り物: " + name);
    }
}

// 抽象クラスを継承するクラス: 車
class Car extends Vehicle {
    public Car(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + "がエンジンを吹かして移動します。ブーン!");
    }
}

// 抽象クラスを継承するクラス: 自転車
class Bicycle extends Vehicle {
    public Bicycle(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + "がペダルを漕いで移動します。シャカシャカ!");
    }
}

public class AbstractClassExample {
    public static void main(String[] args) {
        // Vehicle vehicle = new Vehicle("一般的な乗り物"); // エラー!抽象クラスは直接インスタンス化できない

        Vehicle myCar = new Car("マイカー");
        Vehicle myBicycle = new Bicycle("愛用自転車");

        myCar.displayInfo();
        myCar.move();

        myBicycle.displayInfo();
        myBicycle.move();
    }
}

先生: Vehicleクラスはmove()という抽象メソッドを持つため、それ自体は不完全で、直接オブジェクトを作ることができない。しかし、CarやBicycleがVehicleを継承することで、move()メソッドの実装が強制され、name属性やdisplayInfo()メソッドといった共通の機能を再利用できるんだ。


インターフェースと抽象クラスの使い分け

先生: さて、インターフェースも抽象クラスも、どちらも「未実装のメソッド」を持つことができるし、共通の振る舞いを強制するのに役立つ。じゃあ、どう使い分ければいいと思う?

ボット君: うーん、どちらも似ているから、迷っちゃいますね…。

先生: 良い質問だ!使い分けのポイントはいくつかあるよ。



先生: まとめると、

  • インターフェース:

    • ~としての振る舞いができる」という契約を定義したい場合に使う。

    • 複数のクラスに共通の機能を持たせたいが、それらのクラス間に「is-a」関係(継承関係)がない場合や、複数の共通機能をクラスに持たせたい場合(多重継承の代替)に特に有効。

    • 例:「飛ぶことができる(Flyable)」「泳ぐことができる(Swimmable)」。

  • 抽象クラス:

    • 共通の基盤」を提供し、かつ一部の共通実装も持たせたい場合に使う。

    • 複数のクラスが「~の一種である」という強い「is-a」関係を持ち、共通の属性や処理ロジックも持っている場合に有効。

    • 例:「動物(Animal)」「図形(Shape)」。

ボット君: なるほど!インターフェースは「能力」や「役割」を定義する感じで、抽象クラスは「共通の親」になる感じなんですね!多重継承の有無も大きな違いだ!

先生: その通り!これらを適切に使い分けることで、より柔軟で、拡張しやすく、そして変更に強いプログラムを設計できるようになるんだ。


今日のまとめ!

  • インターフェース(Interface):

    • クラスが守るべき「契約」を定義する。

    • 抽象メソッドの他、デフォルトメソッド、スタティックメソッド(Java 8以降)も持てる。

    • implementsキーワードでクラスに実装させる。

    • 複数のインターフェースを実装できる(多重継承の代替)。

    • 何をすべきか」を強制する。

  • 抽象クラス(Abstract Class):

    • 不完全なクラス」または「共通の基盤」を提供する。

    • 抽象メソッドと完全に実装されたメソッドの両方を持てる。

    • extendsキーワードでクラスに継承させる。

    • 直接オブジェクトを生成できない

    • 共通部分」を提供しつつ、「一部の実装」を子クラスに任せる。

  • 使い分けのポイント:

    • インターフェース: 「~としての振る舞いができる」という契約定義、多重継承の代替。

    • 抽象クラス: 「~の一種である」という共通基盤、共通の属性や一部の実装も持たせたい場合。


ボット君: 先生、今日はインターフェースと抽象クラスについて、すごくよく分かりました!設計の約束事って、プログラムをきれいに保つためにすごく大切なんですね!

先生: その調子だ、ボット君!これらを使いこなすことで、君のプログラムはさらに洗練されたものになるはずだ。次回は、オブジェクト指向の設計をさらに効率的に行うための「デザインパターン」という考え方、特に「オブジェクト作りの『工場』」とも呼ばれるパターンについて学んでいこう!

ボット君: オブジェクト作りの工場!なんだかワクワクします!楽しみです!ありがとうございました!


次回:第10回: オブジェクト作りの「工場」?~デザインパターンとファクトリーメソッドパターン~

#オブジェクト指向 #プログラミング初心者 #Java #クラスとメソッド #プログラミング学習 #入門

いいなと思ったら応援しよう!

ナオユキ|物語と知識のコンシェルジュ もしこの記事が少しでも役に立ったり、楽しんでいただけたりしたら、チップをいただけるととても嬉しいです! いただいたチップは、今後のより良いコンテンツ制作のための書籍代📚とnote購入🗒️、他の方へのチップ代💰に使わせていただきます。 いつも応援ありがとうございます!