🐥

初心者がインターフェースの意義を感じた話

2024/11/09に公開
2

Javaを学んでいる中で、インターフェースという概念が出てきました。しかし、インターネットで調べたり、AIに聞いたりしても実装方法や注意点の解説が主で、実際の「存在意義」を理解するには苦労しました。Java初心者として、クラスと絡めて考えるとインターフェースの意義がより明確に理解できたので、ここでそのポイントを共有します。

クラスとインターフェースの役割

まず、クラスは「オブジェクトの属性(変数)と動作(メソッド)をまとめて管理」するために使われ、特定の概念や物を表現します。例えば、Baseball、Bowling、Javelin Throwといったスポーツにおいて、それぞれが固有の属性や動作を持っています。これを次のように表にまとめられます。

name 属性 メソッド
Baseball pitcher throw
Bowling player throw
Javelin Throw thrower throw

インターフェースは、異なるクラスに共通の「振る舞い」(メソッド)を提供するために存在します。例えば、throwという動作が複数のクラスで必要ならば、Throwableインターフェースとして定義して各クラスに適用することができます。これにより、「投げる」という動作は一貫して提供され、クラスごとにその具体的な内容(例えば、投球の速度やボールの重さなど)をカスタマイズできます。

このように、クラスは横軸、インターフェースは縦軸を管理する役割を持つと考えられます。また、クラスは複数のインターフェースを実装できるため、クラスごとに異なる振る舞いを付加できます。

name 属性 メソッド1 メソッド2
Baseball pitcher throw isSuccess
Bowling player throw isSuccess
Javelin Throw thrower throw isSuccess

さらに、各クラスごとに固有のメソッドを追加することも可能です。

name 属性 メソッド1 メソッド2 メソッド3
Baseball pitcher throw isSuccess getPitchCount
Bowling player throw isSuccess isSecondThrow
Javelin Throw thrower throw isSuccess checkWindConditions

抽象クラスとの違い

このように考えると、抽象クラスとの使い分けも理解しやすいと思います。抽象クラスもインターフェースも概要だけ決めて詳細は各クラスで定義しますが、インターフェースはクラスを横断して振る舞いを提供するのに対し、抽象クラスは属性や基本的なメソッドの共有を目的としています。

Differences from Abstract Classes

インターフェースのメリット

インターフェースをclass クラス名 implements インターフェース名{}で実装することで、必須メソッドの未実装を防ぎ、異なるクラス間でも共通のメソッドが存在するため、コードの理解がしやすくなります。Javaプログラムを本格的に作成した経験はまだありませんが、共通メソッドの「定義書」としてインターフェースが役立つという理解ができたことは大きな収穫でした。

Discussion

fmfm

ふと、自分の記事からリンクが見れたのでコメント致しました。

Go言語のインターフェースも学ぶと面白いですよ。
Javaは
「振る舞い(メソッド)も宣言(implements)もクラスに必要」
ですが、Go言語は
「振る舞い(メソッド)が全て揃っていれば宣言(implements)が不要」
となります。
よくある比喩で言うと、

 Java → 「みゃー」と鳴いて「猫の顔をかぶっている」 → 猫
 Go  → 「みゃー」と鳴く → 猫

というみなし方です。
なので、Javaは変数の扱い方も面倒になります。
以下のサンプルで II2 は同一のメソッドシグネチャを持つのに、
変数で C を参照できるのは I だけです。

public class Main
{
    public static void main(String[] args) {
        new C().m();
        I i = new C();      // OK
        I2 i2 = new C();    // NG
    }
}

interface I {
  void m();
}

class C implements I {
  public void m() { System.out.println("C"); }
}

interface I2 {
  void m();
}

また、インターフェースの実用としては「依存性の逆転」という難しい言葉が使われるケースもあり、
要は「クラス同士の依存関係をインターフェース一つに集中させよう」とワンクッション置く設計の考え方があります。
これがあると、「インターフェースの変更が無い限りは実装クラスの修正が楽になる」という特徴があります。
特に独自コードというよりは、外部のAPI等を扱う時に差し替えやすくするような使い方をします。
(リリース後の保守プロセス等を想像すると、よりメリットが分かり易くなります。「安定稼働しているサービスに影響範囲の広い変更を入れ込む恐怖」とかです)
大規模開発であれば、担当者と機能も大量になるので、お互いの変更が 直接 影響しあわないようにインターフェースを設計するのが慣習です。

また、インターフェースは default メソッドも扱えるようになり、
共通的に実装したい処理をわざわざインターフェースの外(個々の実装クラスや、共通のユーティリティクラス)に書く必要が無くなりました。
インターフェースの事を良く理解しようとされているようでしたので、老婆心ながら長文コメント致しました。失礼致します。

YoshiYoshi

コメントありがとうございます🙏

ですが、Go言語は
「振る舞い(メソッド)が全て揃っていれば宣言(implements)が不要」
となります。

言語によってインターフェースの設計思想が違うのは面白いですね。自分はまだjavaの勉強に取り組み始めたばかりですが、そのようなお話を伺うことができ、 Go言語にも興味が湧いてきました。

特に独自コードというよりは、外部のAPI等を扱う時に差し替えやすくするような使い方をします。
大規模開発であれば、担当者と機能も大量になるので、お互いの変更が 直接 影響しあわないようにインターフェースを設計するのが慣習です。

自分は分析畑のキャリアだったので、このあたりの実務ベースの利点や慣習のお話は大変腑に落ちました。もっと精進せねばですね。