今日は、継承可能なシングルトンクラスについてです。
一般的なシングルトンパターンは、private static なインスタンスフィールドの初期化子を使って生成したインスタンスを、プロパティから参照することで実装します。
でも、シングルトンな管理クラスを作成して、その派生クラスに独自処理を実装したいとします。
ジェネリックを駆使すると、万人向けにならないので、ある程度はテンプレートコーディングとして考えた場合にどうあるか。
ということで以下のサンプルです。
static なメンバは継承できないので、インスタンスを保持する instance フィールドは基底クラスのものをそのまま派生クラスでも使います。
通常のシングルトンパターンでは初期化子で new しますが、それは行わずに、プロパティ側でインスタンスを生成するのがミソです。
このとき、ダブルチェックロッキングパターンを用いたいのですが、タイミング的に lock(object) に渡す適切なオブジェクトがありません。
AppDomain.CurrentDomain でも成立するのですが、非同期に複数のシングルトンインスタンスを生成しようとするとパフォーマンスが落ちそうだねという話もあり、最も軽い object 型のオブジェクトをダミーでぶら下げて lock に使います。
このクラスを別のアセンブリ等で継承させて使うときには、Instance プロパティを new して処理を上書きます。
そして、インスタンス自体は基底クラスの instance フィールドを使いながらも、このプロパティで生成・取得する際には自身の型にキャストすることで、継承されたシングルトンであることがうまいこと隠れるという寸法です。このプロパティはジェネリックで書ければスマートかもしれませんが(できるのだろうか?)、たくさんの開発者で見通しの良いプログラムを求める場合はコーディングパターンで縛ってもいいような気がしています。
基底クラス型のフィールドには、派生クラスのオブジェクトが普通に代入できるという部分が、C 言語系からの移行者に対する気づきポイントのような気がします。object 型の変数に代入できるという話と本質は同じなのですが、説明ポイントです。
namespace SingletonSample { /// <summary> /// シングルトンパターンのベースクラスの実装サンプルを提供します。 /// </summary> public class SingletonSampleBase { /// <summary> /// SingletonSampleBase クラスのシングルトン インスタンスを保持します。 /// </summary> protected static SingletonSampleBase instance = null; /// <summary> /// SingletonSampleBase クラスのシングルトン管理用ロックオブジェクトを保持します。 /// </summary> protected static object lockForSingleton = new object(); /// <summary> /// SingletonSampleBase クラスのシングルトン インスタンスを取得します。 /// </summary> /// <value> /// SingletonSampleBase クラスのシングルトン インスタンス。 /// 生成されていない場合は、インスタンスを生成して返します。 /// </value> public static SingletonSampleBase Instance { get { if (instance == null) { lock (lockForSingleton) { // ダブルチェックロッキングとすることで、パフォーマンスの確保と // インスタンス生成が複数発生しないことを両立する。 if (instance == null) { instance = new SingletonSampleBase(); } } } return instance; } } /// <summary> /// SingletonSampleBase クラスのシングルトン インスタンスを初期化します。 /// </summary> /// <remarks> /// シングルトンパターンを採用するため、コンストラクタは公開しません。 /// 継承を考え、protected としています。 /// </remarks> protected SingletonSampleBase() { // NOP // protected とすることで、外部からのインスタンス生成を防いでいるので、 // 何もないからといってコンストラクタを削除してはならない。 } } /// <summary> /// シングルトンパターンの実装サンプルを提供します。 /// </summary> public class SingletonSample : SingletonSampleBase { /// <summary> /// SingletonSample クラスのシングルトン インスタンスを取得します。 /// このプロパティは、基底クラスの実装を隠します。 /// instance フィールドは基底クラス型なので、本クラス内での参照では /// キャストされたこのプロパティを利用してください。 /// </summary> /// <value> /// SingletonSample クラスのシングルトン インスタンス。 /// 生成されていない場合は、インスタンスを生成して返します。 /// </value> public new static SingletonSample Instance { get { if (instance == null) { lock (lockForSingleton) { if (instance == null) { // ダブルチェックロッキングとすることで、パフォーマンスの確保と // インスタンス生成が複数発生しないことを両立する。 instance = new SingletonSample(); } } } // instance フィールドに格納されている型は基底クラスのため、 // 派生クラスにキャストして返す。 return (SingletonSample)instance; } } /// <summary> /// SingletonSample クラスのシングルトン インスタンスを初期化します。 /// </summary> /// <remarks> /// シングルトンパターンを採用するため、コンストラクタは公開しません。 /// 継承を考え、protected としています。 /// </remarks> protected SingletonSample() { // NOP // protected とすることで、外部からのインスタンス生成を防いでいるので、 // 何もないからといってコンストラクタを削除してはならない。 } } }
コメントする