Java 8で導入された「デフォルトメソッド(default)」。
これは単なる新機能ではなく、私たちが長年悩まされてきた**「共通処理をどこに書くべきか」**という問題に対する、Javaからのスマートな回答です。
今回は、「属性(状態)を持たない共通処理」をインターフェースに集約するメリットを、管理やテストの効率化という視点で解説します。
1. なぜ「〇〇Util」への丸投げが問題になるのか?
多くのプロジェクトでは、StringUtil や DateUtil といった共通クラスを作ります。しかし、これにはいくつかの課題があります。
「モノ」と「振る舞い」が離れ離れになる
本来、そのデータ自身ができるはずの操作(例:自分のデータをCSV形式にする)が、遠く離れたUtilクラスに書かれるため、コードの全体像が把握しづらくなります。
ユーティリティクラスの肥大化
「どこに書くべきか迷った便利メソッド」がすべて特定のUtilクラスに集まり、数千行の巨大なファイルになりがちです。
テストの柔軟性が下がる(staticメソッドの壁)
Utilクラスのメソッドは通常
staticです。staticメソッドはテスト時に中身を差し替える(モック化する)のが難しく、テストがそのUtilの実装に強く依存してしまいます。
2. デフォルトメソッドによる「共通機能のトッピング」
デフォルトメソッドを使えば、共通処理を関連する**「インターフェース」**の中に戻すことができます。
具体例:CSV出力機能の実装
public interface CsvExportable {
// 実装クラスには「CSVにしたいデータ配列」を用意させる
String[] getCsvData();
// 共通の振る舞い:属性(フィールド)が不要な加工処理をここに書く
default String toCsvRow() {
return String.join(",", getCsvData());
}
}
3. 設計のコツ:デフォルトメソッドとUtilクラスの使い分け
共通処理の置き場所に迷ったら、**「その処理は、特定の型が持つべき『振る舞い』か? それとも、どこからでも使える『汎用的な道具』か?」**という視点で整理しましょう。
① デフォルトメソッド:「特定の型に閉じられた振る舞い」
その処理が、特定のインターフェースが表す役割(ドメイン)にのみ関係する**「振る舞い(Behavior)」**である場合は、デフォルトメソッドを使います。
設計意図: 「『CSV出力可能(
CsvExportable)』という型である以上、当然持っているべき共通の操作」を定義する。例: CSVの行生成、データのバリデーションロジックなど。
メリット: ロジックがインターフェース内に隠蔽(カプセル化)されるため、関係のないクラスから不要なメソッドが見えず、設計がクリーンになります。
② Utilクラス:「型に依存しない横断的な道具」
その処理が特定の役割(型)には依存せず、システム全体で共通して利用される**「横断的な道具」**である場合は、Utilクラスが適しています。
設計意図: 「特定のオブジェクトの状態や役割に関わらず、静的に処理を完結させたい汎用ツール」として定義する。
例: ログ出力(
LogUtil)、日付の変換(DateUtil)、数学的な計算処理。メリット: システムのどこからでも
Util.method()と呼ぶだけで即座に利用でき、インフラ層のコードとして再利用性が高まります。
| 項目 | デフォルトメソッド | Utilクラス |
| 設計概念 | 振る舞い(Behavior) | ユーティリティ(Tool) |
| 対象 | 特定の型にのみ関係する | システム全体(型に依存しない) |
| テスト | メソッドのオーバーライドにより差し替え可能 | 結合度が強く、差し替えが困難(static) |
4. まとめ
Utilクラス(static)への過度な依存は、テストの柔軟性を奪い、保守性を下げる原因になる。
特定の型(役割)にのみ関係する共通の振る舞いは、デフォルトメソッドとして実装する。
システム全体で使う横断的な処理は、今まで通りUtilクラスとして実装する。
「とりあえずUtilクラス」を卒業して、「これはどの責務に属する振る舞いか?」を意識する。それだけで、あなたのコードはもっとテストしやすく、オブジェクト指向らしい洗練されたものになるはずです。
0 件のコメント:
コメントを投稿