
こんにちは。インテグレーションサービス本部の横山です。
システムのあちこちから散発的に参照されるクラスがあるとき、いちいちインスタンス化したり、DIしたりするのは煩雑です。
このような場合、クラスをインスタンス化せず、staticメソッドを提供するというのはよくあるパターンですが、外部リソース(たとえばDBアクセス)を伴う処理をstaticメソッドに記述するのはアンチパターンです。
そこで、処理の実体はSpringのBeanに実装し、これをstaticメソッドで呼び出す方法を2パターン考えてみました。
それぞれのパターンで、呼び出し元の変更なしに実装を交換することが可能なようになっています。
クラス図は以下のようになります。破線の範囲がそれぞれのパターンのクラスです。
サンプルなので、クラスの機能は、キーを渡したら値を返すという単純なものです。外部に定義した設定値を取得する用途を想定していますが、そのあたりの実装は本筋に関係ないので省略しています。

パターン1.抽象クラスを定義し、継承クラスをBeanにする。
【抽象クラス】
このクラスにstaticメソッドを用意します。
処理の実体は継承クラスに実装し、そのインスタンスを抽象クラスのクラス変数に格納します。
package sample.singleton1;
/*******************************************************************************
* 設定取得クラス①。
******************************************************************************/
public abstract class SettingAccessor1 {
// クラス変数
/** 唯一のインスタンス(実装クラスのインスタンスを代入する) */
private static SettingAccessor1 instance;
/*****************************************************************************
* キーに対応する値を取得する。
****************************************************************************/
public static String getValue(String key) {
// 内部メソッドを実行する。
return instance.doGetValue(key);
}
/*****************************************************************************
* インスタンスを生成する(実装クラスから参照可)。
****************************************************************************/
protected SettingAccessor1() {
// クラス変数にインスタンスを代入する。
instance = this;
}
/*****************************************************************************
* キーに対応する値を取得する(抽象内部メソッド)。
****************************************************************************/
protected abstract String doGetValue(String key);
}
【継承クラス】
抽象メソッドに処理を実装します。
package sample.singleton1;
/*******************************************************************************
* 設定取得クラス①の実装クラスA。
******************************************************************************/
public class SettingAccessor1ExtA extends SettingAccessor1 {
/*****************************************************************************
* キーに対応する値を取得する(抽象内部メソッドの実装)。
* @see sample.singleton1.SettingAccessor1#doGetValue(java.lang.String)
****************************************************************************/
@Override
protected String doGetValue(String key) {
return this.getClass().getName() + "が呼び出されました。";
}
}
【Bean設定ファイル(抜粋)】
実装を交換するときは、抽象クラス(SettingAccessor1)を継承したクラスのいずれかをBean指定します。
複数の実装を共存させることはできません。
<!-- 設定取得クラス① -->
<bean scope="singleton" class="sample.singleton1.SettingAccessor1ExtA" />
<!-- <bean scope="singleton" class="sample.singleton1.SettingAccessor1ExtB" /> -->
パターン2.実装をBeanに分離する。
【呼び出し用クラス】
クラスのロード時にインスタンスを生成して、定数に格納します。
また、あらかじめ指定されたIDのBeanを取得して、インスタンス変数に格納します。
Beanを取得するには、前回のサンプルと同じResourceFactoryクラスを使用します。
package sample.singleton2;
import sample.springbeanrunner.ResourceFactory;
/*******************************************************************************
* 設定取得クラス②。
******************************************************************************/
public class SettingAccessor2 {
// 定数
/** 唯一のインスタンス */
private static final SettingAccessor2 INSTANCE = new SettingAccessor2();
// インスタンス変数
/** エンジン */
private SettingAccessor2Engine engine =
ResourceFactory.getResource(SettingAccessor2Engine.BEAN_ID);
/*****************************************************************************
* キーに対応する値を取得する。
****************************************************************************/
public static String getValue(String key) {
// エンジンのメソッドを実行する。
return INSTANCE.engine.getValue(key);
}
}
【Bean用インターフェース】
ビーンIDはここに宣言します。
package sample.singleton2;
/*******************************************************************************
* 設定取得クラス②用エンジンのインターフェース。
******************************************************************************/
public interface SettingAccessor2Engine {
// 定数
/** ビーンID */
public static final String BEAN_ID = "settingAccessor2Engine";
/*****************************************************************************
* キーに対応する値を取得する。
****************************************************************************/
public String getValue(String key);
}
【実装クラス】
上記のインターフェースを実装するクラスです。
package sample.singleton2;
/*******************************************************************************
* 設定取得クラス②用エンジンの実装クラスA。
******************************************************************************/
public class SettingAccessor2EngineImplA implements SettingAccessor2Engine {
/*****************************************************************************
* キーに対応する値を取得する。
****************************************************************************/
@Override
public String getValue(String key) {
return this.getClass().getName() + "が呼び出されました。";
}
}
【Bean設定ファイル(抜粋)】
実装を交換するときは、インターフェース(SettingAccessor2Engine)を実装したクラスのいずれかを、指定されたIDで定義します。
こちらも、複数の実装を共存させることはできません。
<!-- 設定取得クラス②用エンジン -->
<bean id="settingAccessor2Engine" scope="singleton" class="sample.singleton2.SettingAccessor2EngineImplA" />
<!-- <bean id="settingAccessor2Engine" scope="singleton" class="sample.singleton2.SettingAccessor2EngineImplB" /> -->
呼び出し先と実装がきちんと分離されているという点ではパターン2が良さそうですが、ResourceFactoryを用意する必要があります。
簡便さを優先するならパターン1でしょうか。
そのへんはケースバイケースというところで、また次回……