C# のコードリーディングをしているときに、Service Locator が出てきて、「どんなパターンだっけ?」と思ったので、しっかり理解するために、サンプルを書いてみた。また、そこに出てきた Lazy
, Activator
のテクニックに関しても調べてみた。
オリジナルのパターン
確かオリジナルは、J2EE patterns じゃないかと思う。このパターンは、サービスをファクトリするのだけど、ServiceLocator
が直接Serviceの実装クラスをファクトリするのではなく、InitialContext
が生成や、リモートコールへのリファレンスを返すようにするのがツボになっている。また、サービスの参照に関しては、キャッシュにより保持されるので、二回目からの呼び出しからはキャッシュが使われる。特に実装クラスが、リモートコールの場合はパフォーマンスも改善されるという具合だ。
現在の実装
ただ、J2EE Patterns は随分古いパターンで、当時は EJB という仕組みがあった。その名残を引き継いでいる。しかし、言語も進化しているので、現在ではもう少しクールに実装できる。InitialContext とか、キャッシュが消えている。しかし、パターンの本質は変わらないようになっている。次からコードを見ていこう。
コードの解説
Lazy 句
Lazy 句を使うと、Lazy Initialization が可能になる。どういうことかとういと、例えばリモートコールをしてデータを取得するメソッドがあるとして、そのメソッドを毎回呼んでいたらコストがかかる。もしかすると、使われないかもしれない。そういう時に、メソッドが本当に呼ばれたときだけ、その中身を実行するというイディオムだ。
Lazy 句を使うとそれが実装できる。ここでいうと、Singleton のインスタンスを初期化するときに、Lazy 句を使っているが、() => new ServiceLocator();
が実際に実行されるのは、instance.Value
が呼ばれたタイミングになる。つまり、*.Value
が呼ばれない限りは、Lazy の引数の中の関数は実行されない。
public class ServiceLocator
{
static readonly Lazy<ServiceLocator> instance = new Lazy<ServiceLocator>(() => new ServiceLocator());
:
public static ServiceLocator Instance => instance.Value;
Lazy 句のもう一つ良いところは、スレッドセーフであるということだ。例えば、このような仕組みを実装する場合、instance が null かチェックするメソッドを書いて、nullならインスタンス生成とかもできるが、そうすると、マルチスレッドの環境では、タイミングが悪ければ何回もインスタンス化が走ることになる。
Lazy の実装では、しっかりロックをかけてくれるので自分で排他制御を実装する必要がないところがイケている。
Activator 句
もう一つの自分にとっての新しい構文は、Activator
句だ。これはいわゆるリフレクションで、引数の型のインスタンスを初期化するようになっている。ここでは、ジェネリクスで、サービスのインターフェイスと、実装クラスを指定すると、ディクショナリに、登録してくれるようになっている。
public void Register<ServiceInterface, ServiceImplementation>() where ServiceImplementation : new ()
{
registeredServices[typeof(ServiceInterface)] =
new Lazy<object>(() => Activator.CreateInstance(typeof(ServiceImplementation)));
}
実際使うときは GetService<T>
を使う。ここで、私は、IService
じゃないんだー。と思ったけど、ジェネリクスで書いているので、この ServiceLocator は、IService
にすら依存していない。だからクラス図も依存しないように書いている。
public T GetService<T>() where T : class
{
Lazy<object> service;
if (registeredServices.TryGetValue(typeof(T), out service))
{
return (T)service.Value;
}
throw new ArgumentException("Specified Service not found");
}
使う側はこんなコードになる。DIしているのに近いイメージ。
ServiceLocator.Instance.Register<IService, OsakaService>();
var service = ServiceLocator.Instance.GetService<IService>();
Console.WriteLine(service.Greeting());
Console.ReadLine();
結果は
もうかりまっか
になる。コードの全体像は次のとおり。
よし、しっかり理解できたかな。