web-dev-qa-db-ja.com

DIを使用した1つのインターフェイスの複数の実装

現在、AutofacのIOCコンテナを使用して、依存性注入パターンを自分自身に教えようとしています。以下に示す非常に簡単な例を考え出しました。例は単純ですが、正しく動作させることができません。

これが私のクラス/インターフェースです:

2つのモンスター。どちらもIMonsterインターフェイスを実装しています。

interface IMonster
{
  void IntroduceYourself();
}

class Vampire : IMonster
{
  public delegate Vampire Factory(int age);

  int mAge; 

  public Vampire(int age)
  {
    mAge = age;
  }

  public void IntroduceYourself()
  {
    Console.WriteLine("Hi, I'm a " + mAge + " years old vampire!");
  }
}

class Zombie : IMonster
{
  public delegate Zombie Factory(string name);

  string mName;

  public Zombie(string name)
  {
    mName = name;
  }

  public void IntroduceYourself()
  {
    Console.WriteLine("Hi, I'm " + mName + " the zombie!");
  }
}

それから私の墓地があります:

interface ILocation
{
  void PresentLocalCreeps();
}

class Graveyard : ILocation
{
  Func<int, IMonster>    mVampireFactory;
  Func<string, IMonster> mZombieFactory;

  public Graveyard(Func<int, IMonster> vampireFactory, Func<string, IMonster> zombieFactory)
  {
    mVampireFactory = vampireFactory;
    mZombieFactory  = zombieFactory;
  }

  public void PresentLocalCreeps()
  {
    var vampire = mVampireFactory.Invoke(300);
    vampire.IntroduceYourself();

    var zombie = mZombieFactory.Invoke("Rob");
    zombie.IntroduceYourself();
  }
}

そして最後に私のメイン:

static void Main(string[] args)
{
  // Setup Autofac
  var builder = new ContainerBuilder();
  builder.RegisterType<Graveyard>().As<ILocation>();
  builder.RegisterType<Vampire>().As<IMonster>();
  builder.RegisterType<Zombie>().As<IMonster>();
  var container = builder.Build();

  // It's midnight!
  var location = container.Resolve<ILocation>();
  location.PresentLocalCreeps();

  // Waiting for dawn to break...
  Console.ReadLine(); 
  container.Dispose();
}

そしてこれが私の問題です:実行時に、Autofacは次の行に例外をスローします:

var vampire = mVampireFactory.Invoke(300);

MVampireFactoryが実際にゾンビをインスタンス化しようとしているようです。もちろん、ゾンビのコンストラクターはintを受け取らないため、これは機能しません。

これを修正する簡単な方法はありますか?それとも、Autofacの動作が完全に間違っているのでしょうか?この問題をどのように解決しますか?

23
Boris

制御の反転は、それ自体が工場ではありません。あなたのケースはファクトリーパターンにぴったりです。

モンスターの作成に使用される新しい抽象ファクトリを作成します。

public interface IMonsterFactory
{
    Zombie CreateZombie(string name);
    Vampire CreateVampire(int age);
}

そして、その実装をAutofacに登録します。

最後に、クラスでファクトリを使用します。

class Graveyard : ILocation
{
  IMonsterFactory _monsterFactory;

  public Graveyard(IMonsterFactory factory)
  {
    _monsterFactory = factory;
  }

  public void PresentLocalCreeps()
  {
    var vampire = _monsterFactory.CreateVampire(300);
    vampire.IntroduceYourself();

    var zombie = _monsterFactory.CreateZombie("Rob");
    zombie.IntroduceYourself();
  }
}

もちろん、必要に応じて特定のモンスターファクトリーを使用することもできます。それでもなお、インターフェースを使用すると、コードがはるかに読みやすくなります。

更新

しかし、どのようにファクトリを実装しますか?一方では、ファクトリはIOCコンテナを使用してモンスターを作成しないでください。これは悪と見なされるためです(DIパターンをサービスロケータのアンチパターンに劣化させます)。

SLがアンチパターンであると聞いてとてもうんざりしています。そうではありません。すべてのパターンと同様に、誤って使用すると不利になります。これはすべてのパターンに当てはまります。 http://blog.gauffin.org/2012/09/service-locator-is-not-an-anti-pattern/

しかし、この場合、ファクトリで直接実装を作成できない理由がわかりません。それが工場の目的です:

public class PreferZombiesMonsterFactory : IMonsterFactory
{
    public Zombie CreateZombie(string name)
    {
        return new SuperAwesomeZombie(name);
    }

    public Vampire CreateVampire(int age)
    {
        return new BooringVampire(age);
    }
}

それ以上に複雑ではありません。

一方、工場はモンスター自体を作成しないでください。IOCコンテナをバイパスし、工場とモンスターを緊密に結合するためです。それとも私は再び間違った方向に進んでいますか? ;-)

工場がモンスターの実装と緊密に結合されていることは問題ではありません。これがファクトリの目的であるためです。オブジェクトの作成を抽象化して、コード内の他の何も具体的なことを認識しないようにすることです。

SuperDeluxeMonsterFactoryMonstersForCheapNonPayingUsersFactoryなどを作成できます。アプリケーション内の他のすべてのコードは、(異なるファクトリを使用して)異なるモンスターを使用していることを認識しません。

コンクリートを変更する必要があるたびに、工場を切り替えるか、既存の工場を変更するだけです。モンスターの実装がリスコフの置換原則に違反しない限り、他のコードは影響を受けません。

工場とIoCコンテナ

では、ファクトリとIoCコンテナの違いは何ですか? IoCは、クラスの依存関係を解決し、存続期間を維持するのに優れています(たとえば、HTTPリクエストが終了すると、コンテナーはすべてのディスポーザブルを自動的に破棄できます)。

一方、ファクトリはオブジェクトの作成に優れています。それはそれだけで他には何もしません。

概要

したがって、コードのどこかで特定のタイプの実装を取得する必要がある場合は、通常、ファクトリを使用する必要があります。ファクトリ自体は、IoCをサービスロケータとして内部的に使用できます(依存関係を解決するため)。これは、アプリケーションの他の部分に影響を与えないファクトリでの実装の詳細であるため、問題ありません。

サービスを解決する場合(および、取得する実装を気にしない場合、または以前に作成されたインスタンスを取得する場合)は、(依存性注入を介して)IoCコンテナーを使用します。

29
jgauffin