Help us understand the problem. What is going on with this article?

Service Locator、Lazy、Activator の調査

More than 1 year has passed since last update.

C# のコードリーディングをしているときに、Service Locator が出てきて、「どんなパターンだっけ?」と思ったので、しっかり理解するために、サンプルを書いてみた。また、そこに出てきた Lazy, Activator のテクニックに関しても調べてみた。

オリジナルのパターン

確かオリジナルは、J2EE patterns じゃないかと思う。このパターンは、サービスをファクトリするのだけど、ServiceLocator が直接Serviceの実装クラスをファクトリするのではなく、InitialContext が生成や、リモートコールへのリファレンスを返すようにするのがツボになっている。また、サービスの参照に関しては、キャッシュにより保持されるので、二回目からの呼び出しからはキャッシュが使われる。特に実装クラスが、リモートコールの場合はパフォーマンスも改善されるという具合だ。

クラス図0.jpg

現在の実装

ただ、J2EE Patterns は随分古いパターンで、当時は EJB という仕組みがあった。その名残を引き継いでいる。しかし、言語も進化しているので、現在ではもう少しクールに実装できる。InitialContext とか、キャッシュが消えている。しかし、パターンの本質は変わらないようになっている。次からコードを見ていこう。

クラス図0.jpg

コードの解説

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();

結果は

もうかりまっか

になる。コードの全体像は次のとおり。

よし、しっかり理解できたかな。

Resources

TsuyoshiUshio@github
プログラマ。自分の学習用のブログです。内容は会社とは一切関係ありません。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away