設計パターンと歴史的背景
設計パターンは、コードの構造を大きく変更するというよりも、既存の実装をより適切なレイアウトに再配置し、最小限の変数増減で効率的化する手法と言えます。
この概念は建築分野から由来しており、オブジェクト指向ソフトウェアデザインに関する「四人組(Gang of Four)」による書籍『Design Patterns』によって広まりました。同書には 23 の代表的なパターンが記載されています。
主要な設計原則:SOLID
優れた設計を実現するための指針として、頭文字を取って SOLID と呼ばれる原則があります。
- SRP(単一責任の原則): 一つのクラスやモジュールは、変化する理由を一つに持つべきです。
- OCP(開放閉鎖の原則): クラスの拡張に対しては開き、修正に対しては閉じているべきです。
- LSP(リスコフの置換原則): 継承されたサブタイプは、基本クラスの型として使用しても動作が破綻しないように振る舞う必要があります。
- ISP(インターフェース分離の原則): 特定のクライアントのための狭小なインターフェースを用意することが、巨大なインターフェースを強制することより優れています。
- DIP(依存関係逆転の原則): 詳細の実装ではなく、抽象化(インターフェース等)に依存するべきです。
クリエイティブ(生成)パターン
オブジェクトのインスタンス化プロセスを管理するパターン群です。
シングルトンパターン
クラスインスタンスの生成数を 1 つに制限し、グローバルなアクセスポイントを確保します。
package com.example.config;
/**
* シングルトンパターン:初期化時の設定管理
*/
public class SystemConfiguration {
/**
* クローロード時に即座にインスタンス化される静的メンバ
*/
private static final SystemConfiguration MANAGER = new SystemConfiguration();
private SystemConfiguration() {
// 外部からの直接インスタ化を防止
}
public static SystemConfiguration getInstance() {
return MANAGER;
}
}
ここでは、メソッドが静的であるため、それ自体が所属するオブジェクトを持てないという特性を理解する必要があります。static メンバはクラスライフサイクルに紐付くため、シングルトンインスタンスもまた static で定義されなければなりません。
ファクトリーパターン
オブジェクト生成ロジックをカプセル化し、呼び出し元にそれを隠蔽します。
シンプルファクトリー
引数の条件分岐に基づいて具体的な実装クラスを返します。
package com.example.factory;
import com.example.factory.service.EmailNotifier;
import com.example.factory.service.SMSNotifier;
import com.example.factory.service.NotificationService;
public class NotificationFactory {
public static NotificationService getService(String type) {
if (type == null) {
return null;
}
switch (type.toLowerCase()) {
case "email":
return new EmailNotifier();
case "sms":
return new SMSNotifier();
default:
return null;
}
}
}
ファクトリーメソッドパターン
サブクラスがインスタンス化するロジックを決定します。
// インターフェース定義
public interface DataProcessor {
void process();
}
// 具体的実装
public class JsonProcessor implements DataProcessor {
@Override
public void process() { /* JSON 処理ロジック */ }
}
// ファクトリ実装
public class ProcessorFactory {
public DataProcessor createProcessor() {
return new JsonProcessor();
}
}
アブストラクトファクトリー
関連する一連の製品群を生成するスーパーファクトリーです。
public abstract class UIComponentFactory {
public abstract Button createButton();
public abstract Menu createMenu();
}
public class WindowsFactory extends UIComponentFactory {
@Override
public Button createButton() { return new WinButton(); }
@Override
public Menu createMenu() { return new WinMenu(); }
}
ビルダーパターン
複数の手順を経て構成を持つ複雑なオブジェクトを作成する際に向いています。
標準ライブラリの StringBuilder や、Lombok の @Builder アノテーションなどが該当します。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserOrder {
private String userId;
private BigDecimal amount;
private LocalDateTime timestamp;
}
// 使用例
UserOrder order = UserOrder.builder()
.userId("U001")
.amount(new BigDecimal("1000"))
.timestamp(LocalDateTime.now())
.build();
注意すべき点として、Lombok の @Builder を併用する場合、デフォルトコンストラクタが生成されない可能性があります。フレームワークとの互換性を保つためには、@NoArgsConstructor などとの併用が推奨されます。
その他のパターン分類
構造パターン: クラスやオブジェクトを組み合わせる方法について扱います。例えばプロキシパターンは、実際の対象へのアクセス制御を行います。
振る舞いパターン: オブジェクト間の通信責任を明確化します。
- チェーンオブレポンシビリティ: リクエストを送信元から順次処理します。ここで重要なのは、コンテキストオブジェクトの状態値(例えば注文 ID など)が各段階で異なる場合、これを共有コンテナに格納することは避けるべきです。データ部分は再利用可能ですが、オブジェクト全体のステータスがmutable な場合はスコープを限定する必要があります。