実務の設計レビューやコードを読んでいるとき、「レジストリパターン(Registry Pattern)」というに出てきて、「名前は聞いたことあるけど、具体的にどう実装してどう使うんだっけ…?」となったことはありませんか?
シングルトンやFactoryパターンに比べると少し影が薄い印象がありますが、特定のユースケースでは今でも非常に強力な武器になります。
今回は、Javaを使った具体的な実装例を交えながら、誰でもわかるようにレジストリパターンの基本と使いどころを解説します!
1. レジストリパターンとは?
一言でいうと、「オブジェクト(インスタンス)を共通の場所に登録(レジストリ)しておき、必要なときに名前などをキーにして取り出せるようにするパターン」です。
身近な例で例えるなら、「ホテルのクローク(荷物預かり所)」です。
荷物を預ける(登録:register)
引き換え券(キー:key)をもらう
必要なときに引き換え券を出して荷物を受け取る(取得:get)
シングルトンパターンとの違い
- シングルトン: システム内に**特定のクラスのインスタンスが「絶対に1つ」**しか存在しないことを保証する。
- レジストリ: 複数の異なるインスタンスを1つの管理クラス(レジストリ)にまとめて保持し、キーを使って出し分けられる。
2. Javaでのシンプルな実装例
「文字列のキー」を渡すと、それに応じた「通知処理(サービス)」を返すレジストリを実装してみます。
① インタフェースと実装クラスの準備
まずは、レジストリに登録したいオブジェクトの共通インタフェースを作ります。
public interface MessageService {
void sendMessage(String message);
}
// 実装A: Email送信
public class EmailService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Emailを送信: " + message);
}
}
// 実装B: Slack送信
public class SlackService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Slack通知を送信: " + message);
}
}
② レジストリクラスの実装
Javaの Map を使ってインスタンスを管理するのが一般的です。
import java.util.HashMap;
import java.util.Map;
public class MessageServiceRegistry {
// インスタンスを保持するマップ
private static final Map<String, MessageService> registry = new HashMap<>();
// 登録用メソッド
public static void register(String key, MessageService service) {
registry.put(key, service);
}
// 取得用メソッド
public static MessageService get(String key) {
MessageService service = registry.get(key);
if (service == null) {
throw new IllegalArgumentException("未登録のキーです: " + key);
}
return service;
}
}
③ 実際に使ってみる
アプリケーションの起動時などに登録を済ませ、業務ロジックではキーを指定して取得します。
public class Main {
public static void main(String[] args) {
// 1. 最初に対象のインスタンスを登録しておく
MessageServiceRegistry.register("EMAIL", new EmailService());
MessageServiceRegistry.register("SLACK", new SlackService());
// 2. 動的にキーを受け取って処理を切り替える
String userPreference = "SLACK"; // DBや画面からの入力など、動的に変わる値を想定
// レジストリからインスタンスを取得
MessageService service = MessageServiceRegistry.get(userPreference);
// 実行(振る舞いが動的に切り替わる)
service.sendMessage("こんにちは!"); // 「Slack通知を送信: こんにちは!」が出力される
}
}
3. 実務での「使いどころ」は?(メリット)
実務において、このパターンがなぜ重宝されるのかというと、「大量の if-else や switch 文を撲滅できるから」です。
もしレジストリを使わない場合、以下のような条件分岐コードになりがちです。
// アンチパターン:分岐が増えるたびにこのメソッドを修正する必要がある
public MessageService getService(String type) {
if ("EMAIL".equals(type)) {
return new EmailService();
} else if ("SLACK".equals(type)) {
return new SlackService();
}
throw new IllegalArgumentException();
}
これだと、新しく「LINE通知」を追加したときに、この分岐ロジック自体を修正(変更)しなければならず、拡張に対してクローズ(OCP: 開放閉鎖原則の違反)になってしまいます。
レジストリパターンを使えば、新機能を追加する際は「新しいクラスを作って、最初の登録処理に register するだけ」で済むため、既存の業務ロジックに一切手を加える必要がなくなります。既存システムの置き換え(リプレイス)プロジェクトなど、保守性・拡張性が強く求められる現場で特によく見られます。
4. あれ? SpringのDIコンテナで良くない?
Java(特にSpring Bootなど)を触っていると、それ、DIコンテナがやってることでは?」と思うかもしれません。
結論から言うと、その通りです。
Spring Frameworkの ApplicationContext 自体が、巨大なレジストリパターンの実装体と言えます。
じゃあどうやって棲み分けるの?
現代の実務(特にSpring環境)では、手動で register をガリガリ書くのではなく、DIの「Map注入」機能と組み合わせてスマートにレジストリを自動構築することが多いです。
@Component
public class MessageServiceRegistry {
// SpringがMessageServiceを実装した全Beanを自動でMapに注入してくれる!
// クラス名(先頭小文字)や @Component("名前") で指定した文字列が自動的にキーになります
@Autowired
private Map<String, MessageService> registry;
public MessageService get(String type) {
MessageService service = registry.get(type);
if (service == null) {
throw new IllegalArgumentException("未登録のタイプです: " + type);
}
return service;
}
}
手動での登録漏れが起きず、新しくクラスを追加するだけで自動認識されるため、実務のコードが劇的に綺麗になります。
まとめ
レジストリパターンは、オブジェクトのクローク(預かり所)。
if-else の増殖を防ぎ、拡張性に強いコードが書ける。
現代のJava(Spring等)では、DIコンテナのMap注入機能と組み合わせることで真価を発揮する。
「急に会話に出てきて焦った」という方も、本質は「Mapでインスタンスを管理して出し分けるだけ」なので、ぜひ臆せず実務のコードリーディングや設計に活かしてみてください!