IoCコンテナ(DIコンテナ)のAutofacでHello World!をする (C#)

はじめに

Inversion of ControlコンテナのAutofacを試したのでそのメモを書く。

DI(Dependency injection, 依存性注入)という言葉を知っている人がAutofacはこう使うんだなと雰囲気を知れるくらいを目指す。DI自体やサービスロケータなどについては触れないが次回に書こうと思う。現在はIoCよりDIの方が広く広まっている言葉だと思うが、そのことについてもちらっと触れる。

Autofac

Autofacは.NET向けIoCコンテナであり、クラス間の依存関係を管理するライブラリである。それによってアプリケーションの規模や複雑さが大きくなっても、変更が容易な状態を維持できる。

Autofacのリリースは2007年頃だろうか。Nicholas Blumhardtが前身となったCarawayを発展させてAutofacを作ったようだ*1

使い方の雰囲気を掴むために、Hello WorldアプリをAutofacを使わないものと使うもので比較してみよう。

Autofacを使わないHello World!

using System;

namespace hello_app_cli_non_autofac
{
    class Program
    {
        static void Main(string[] args)
        {
            Greeting();
        }

        public static void Greeting()
        {
            IGreetingWordsCreator creator = new GreetingWordsCreator();
            IGreeter greeter = new Greeter(creator);
            greeter.Greeting();
        }
    }

    public interface IGreeter
    {
        void Greeting();
    }

    public class Greeter : IGreeter
    {
        private IGreetingWordsCreator _creator;
        public Greeter(IGreetingWordsCreator creator)
        {
            this._creator = creator;
        }
        public void Greeting()
        {
            Console.WriteLine(this._creator.Create());
        }
    }

    public interface IGreetingWordsCreator
    {
        string Create();
    }

    public class GreetingWordsCreator : IGreetingWordsCreator
    {
        public GreetingWordsCreator()
        {
        }

        public string Create()
        {
            return "Hello World!";
        }
    }
}

実行

$ dotnet run
Hello World!

クラス図

Autofacを使うHello World!

パッケージ追加

dotnet add package autofac

Autofacを使って書き換えると以下

using System;
using Autofac;

namespace hello_app_cli_autofac
{
    class Program
    {
        private static IContainer Container { get; set; }

        static void Main(string[] args)
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<GreetingWordsCreator>().As<IGreetingWordsCreator>();
            builder.RegisterType<Greeter>().As<IGreeter>();
            Container = builder.Build();
            Greeting();
        }

        public static void Greeting()
        {
            using (var scope = Container.BeginLifetimeScope())
            {
                var greeter = scope.Resolve<IGreeter>();
                greeter.Greeting();
            }
        }
    }

    public interface IGreeter
    {
        void Greeting();
    }

    public class Greeter : IGreeter
    {
        private IGreetingWordsCreator _creator;
        public Greeter(IGreetingWordsCreator creator)
        {
            this._creator = creator;
        }
        public void Greeting()
        {
            Console.WriteLine(this._creator.Create());
        }
    }

    public interface IGreetingWordsCreator
    {
        string Create();
    }

    public class GreetingWordsCreator : IGreetingWordsCreator
    {
        public GreetingWordsCreator()
        {
        }

        public string Create()
        {
            return "Hello World!";
        }
    }
}

書き換え前のIGreeter greeter = new Greeter(creator); はautofacを使うとvar greeter = scope.Resolve<IGreeter>();となっている。ResolveによりIGreeterを実装したGreeterのインスタンスを取得している(今後解決と呼ぶことにする)。その時に必要なGreetingWordsCreatorのインスタンスも同時に生成されている。これができるのはRegisterTypeによりインターフェースに対応するクラスを登録したからである。登録後はContainer = builder.Build();IoCコンテナを生成している。

書き換え前はその場で生成していたが、書き換え後は生成に関するルールは予め設定し、Containerが知っているという状態になっている。そして使う時に解決するという感じだ。

サービスとコンポーネント

Autofacではあるクラスによって必要とされる(依存している)クラスをサービス、使う側をコンポーネントと呼んでいる。つまりコンポーネントもサービスもクラスではあるのだが、使う側か、機能を提供する側かというような観点である。サービスは基本的にクラスではなくインターフェースを指定する。これにより結合度を下げることができる。

IoCコンテナのメリット

インターフェースを実現するクラスが複数あればユーザは自分のアプリケーションに合わせてクラスを選ぶ。また、コンストラクタの引数でインスタンスの挙動が変わるならば必要な引数を指定するだろう。先に述べたとおり、AutofacによりHello Worldアプリは予め設定し、利用する時に解決するという挙動にすることができた。コンポーネントの設定と利用を分離できるのがIoCコンテナを使うメリットの1つである。ここでのメリットとはIoCコンテナのメリットであり、DIのメリットではない。また、このメリットは何のためのメリットかというとソフトウェアが持つべき再利用性や変更容易性に対するメリットである。

再利用性のあるコンポーネントを組み合わせて、変更容易で運用しやすいアプリケーションを開発し事業に寄与していきたいという当然の根本のテーマがあるわけだ。

スコープ

コンテナからgreeterを解決する時にシングルトンのように毎回同じインスタンスを取得するか、毎回生成するか、もしくはusingステートメント内で共通にするかなどいろいろとライフタイムのスコープの設定ができる。これについてはAutofac について調べてみた その1 インスタンスのスコープという記事を牛尾さんが書かれているのでそちらを参照して頂きたい。

IoCとDI

最近ではDIの方が言葉をよく聞くと思うが、Autofacの開発の頃はまだIoCが一般的だったようだ。DIという言葉に関して、Martin FowlerのInversion of Control Containers and the Dependency Injection patternに書かれてあり、記事からIoCという言葉への課題感を引用する。

When these containers talk about how they are so useful because they implement "Inversion of Control" I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.

IoCという言葉の意味が広いため混乱を生む。IoCは性質であり目的ではないという感じだろうか。IoCを支持する人たちと議論をしDIという言葉に落ち着いたらしい。

軽量コンテナ

IoCコンテナ、DIコンテナは軽量コンテナと呼ばれていた。これはJavaアプリケーションの実行環境であるEJBコンテナというものに対するアンチテーゼである。EJBとはミドルウェアを抽象化しビジネスロジックを書くことに専念することができる仕組みである。そのためEJBコンテナは機能が豊富だが複雑でヘビーなコンテナということである。さらにコンポーネントEJBへの依存も強くなる。そこでEJBに依存しない純粋なJava Object (POJO)を使ってアプリケーションを作りたいとう流れになった。そしてEJBから依存関係を解決する機能など一部のみ取り出された軽量なコンテナが作られたということのようである。依存関係をコンストラクタなどで注入するやり方はコードの自動生成もしやすく、POJOを保てて軽量なコンテナを実現できる。IoCという名前に関しては、従来のEJBコンテナから依存関係を取り出す*2というのと逆ということでIoCコンテナという風な名前に最初はなったのだろう。

名前について気になる点

名前について気になるのは、コンテナは実行環境的な意味があったのだろうか?今はフレームワークやライブラリ的な意味を持つだろうか。Autofacのコンテナは依存関係を保持するコンポーネントという意味だろうか。Springの場合のコンテナはDIフレームワークといっていいだろうか。

まとめ

*1:Introducing Caraway, the lightweight, embeddable .NET IoC container

*2:例えば永続化したい時はEJBからそのためのクラスを取得していた