Javaで変数を宣言するとき、以下のように「実装クラス」か「抽象クラス」か、どちらの型で宣言すべきか迷ったことはありませんか?
// 実装クラスで宣言
ArrayList<String> list = new ArrayList<>();
// 抽象クラスやインタフェースで宣言
List<String> list = new ArrayList<>();
この記事では、特に抽象クラスと実装クラスのどちらを使うべきか?という視点から、設計の柔軟性・保守性・テスト性などを踏まえて最適な選択肢を解説します。
結論:原則として抽象クラス型で宣言するのが望ましい
Javaでは、原則として抽象クラスやインタフェースなど「上位型(抽象型)」で変数を宣言することが推奨されます。これは、以下のような理由によるものです。
- 実装を柔軟に差し替え可能
- オブジェクト指向設計原則に適合
- テストやモック化がしやすい
- 設計意図が明示的になる
もちろん、すべてのケースで抽象型が正解とは限らず、例外的に実装クラス型で宣言すべきケースも存在します。次節以降で詳しく見ていきましょう。
抽象クラス型で宣言する4つのメリット
1. 実装の柔軟性を確保できる
変数を抽象クラスで宣言しておけば、実装クラスを差し替えても影響範囲を最小限に抑えられます。
AbstractLogger logger = new ConsoleLogger();
// 将来的に
logger = new FileLogger(); // に変更してもOK
実装クラスで変数を宣言してしまうと、後からの差し替えが難しくなり、リファクタリングに手間がかかります。
2. 設計原則(DIP)に沿った構造になる
「依存性逆転の原則(Dependency Inversion Principle)」では、「高水準モジュールは低水準モジュールに依存すべきではなく、抽象に依存すべき」と定義されています。
抽象クラスやインタフェースを型に使うことで、この原則に則った設計を自然に実現できます。
3. テストやモックがしやすくなる
抽象クラス型で宣言されていれば、ユニットテストでサブクラスやモックオブジェクトを簡単に差し込むことができます。
AbstractService service = new MockService(); // テスト用
これにより、外部依存を切り離したテストが容易になり、CI/CDやTDDとの相性も良くなります。
4. 設計意図をコードで表現できる
抽象型を使うことで、「この変数は特定の実装に依存せず、汎用的な操作しか期待していない」という設計者の意図をコードで明示できます。これにより、チーム開発でも誤解が減り、読みやすいコードになります。
実装クラスで宣言すべきケースとは?
実装クラスでの宣言が完全に悪いわけではありません。以下のようなケースでは、あえて実装クラス型で宣言する選択もアリです。
- 1. その実装クラス特有のAPIを使いたい場合
-
ArrayList<String> list = new ArrayList<>(); list.ensureCapacity(100); // ArrayList特有のメソッド
- 2. 特定の実装を明示したい設計意図がある場合
-
例えば「絶対にArrayListしか使ってはいけない設計」なら、あえて実装クラスで縛ることでその意図を強調できます。
- 3. 非常に限定的な処理で、拡張性より明快さを優先したい場合
-
一時的なユーティリティクラスやテストコードなど。
インタフェース型との違いと使い分け
抽象クラスと混同されがちなのがインタフェースです。両者の使い分けを整理しましょう。
特性 | 抽象クラス | インタフェース |
---|---|---|
状態(フィールド) | 持てる | 持てない(定数のみ) |
実装の継承 | 単一継承のみ | 複数実装可能 |
共通ロジックの再利用 | ○(部分実装あり) | △(defaultメソッド) |
一般的には、以下のように使い分けます。
- 共通のロジック(振る舞い+状態)をまとめたい → 抽象クラス
- 契約(API定義)だけで十分 → インタフェース
そして変数宣言時も同様に、「より抽象度の高い型(インタフェース → 抽象クラス → 実装クラス)」の順に、上位型を使うほど保守性の高い設計になります。
まとめ:原則は抽象型、ただし目的次第で実装型も
Javaで変数を宣言する際には、次のように考えるのがベストです。
- 原則:抽象クラスまたはインタフェースで変数を宣言
- 理由:柔軟性・拡張性・テスト性・設計意図の明確化
- 例外:実装クラス特有の振る舞いが必要な場合や、明示的に固定したい意図がある場合
設計に正解は一つではありませんが、目的と状況に応じて「抽象 or 実装」を使い分けられる判断力こそが、優れたJavaエンジニアの証です。