0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 4-20:抽象クラスよりもインターフェースを選ぶ

Posted at

4-20:抽象クラスよりもインターフェースを選ぶ

要点

新しく型の抽象化を作るときは、まずインターフェースを使う。抽象クラスは「実装の共有」が必要なときにのみ使う。
インターフェースは API(型)を柔軟に定義でき、実装側の自由度が高く、複数継承(型として)は可能にするため、一般に優先されます。

なぜインターフェースを優先するのか

  • 多重型継承が可能:
    クラスは implements で複数のインターフェースを実装できる一方、extends はクラスの単一継承しかできないため、インターフェースのほうが柔軟。
  • 実装から契約を分離できる:
    インターフェースは「何をするか(what)」を定義し、実際の実装(how)は各実装クラスに任せられます。
  • API の後方互換性が作りやすい:
    Java 8 以降は default メソッドを使って既存インターフェースに機能を追加しやすく、既存実装を壊しにくい(ただし衝突や設計上の注意点はあり)。
  • 実装選択の自由:
    クライアントは任意の継承ツリーの中でインターフェースを実装でき、特定の抽象クラスに縛られない。
  • テストやモックが作りやすい:
    インターフェースは実装依存がないため、Mockito 等によるモックやスタブの作成が簡単。

悪い例

// 悪い:API と実装を結びつけてしまう
public abstract class Shape {
    protected int x, y;
    public abstract double area();
    public void moveTo(int nx, int ny) { this.x = nx; this.y = ny; }
}

問題点:

  • Shape を継承しなければ area() を持てない(つまり同じ振る舞いを持ちたい他のクラスが使えない)。
  • 実装共有(moveTo)のためだけに継承を強制してしまい、柔軟さを失う。

良い例

public interface Shape {
    double area();
    void moveTo(int x, int y);
}
  • 任意のクラスが implements Shape して area() を実装できる。
  • 実装を共有したければ別にヘルパークラス(ユーティリティ)やデフォルトメソッド/委譲を使う。

例:共通実装が必要なら

public interface Shape {
    double area();
    void moveTo(int x, int y);

    default void moveBy(int dx, int dy) { // Java8+ の default
        // デフォルト実装を置ける(互換性に配慮)
        throw new UnsupportedOperationException();
    }
}

// 実装共有はヘルパークラスで
public final class ShapeUtils {
    public static void moveTo(Shape s, int x, int y) { ... }
}

抽象クラスを選ぶべき典型ケース

  • 実装共有が本質的に重要で、サブクラスに同じ非trivialなコードを再利用させたいとき。
  • 状態(インスタンスフィールド)を持たせたいとき:インターフェースはインスタンスフィールドを持てない。
  • 新しいメソッドを追加するたびに実装の修正を要求したくない(ただし Java 8 の default があるが万能ではない)。
  • たとえば「部分的に実装される抽象テンプレート」(テンプレートメソッドパターン)のときは抽象クラスが自然。

簡単な例(抽象クラスが適切):

public abstract class AbstractParser {
    private final Buffer buffer; // 状態を持つ
    protected AbstractParser(Buffer b) { this.buffer = b; }
    public void parseAll() { // 共通処理
        while (!buffer.empty()) parseNext();
    }
    protected abstract void parseNext();
}

インターフェースを使う際の注意点

  • default メソッドは乱用しない:
    互換性維持のために便利だが、多重継承時に衝突(どの default を採るか)や設計のあいまいさが生じる。
  • インターフェースは状態(フィールド)を持てない:
    必要なら別途ヘルパーか抽象クラスで実装共有する。
  • 新メソッドの互換性:
    Java 8 以前はインターフェースにメソッドを追加すると実装が壊れる。現代では default で補うが、意味的に正しいか考える。

まとめ

  • まずインターフェース — 型を表すならインターフェースで設計するのが柔軟で将来性が高い。
  • どうしても実装共有や状態が必要なときだけ抽象クラス を使う。
  • Java 8 の default によりインターフェースはさらに強力になったが、設計意図を曖昧にしないよう乱用は避ける。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?