25

现在,我正在尝试使用 Autofac 的 IOC 容器自学依赖注入模式。我想出了一个非常简单的例子,如下所示。虽然这个例子很简单,但我无法让它正常工作。

这是我的类/接口:

两个怪物,都实现了 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 的工作方式完全错误?你将如何解决这个问题?

4

1 回答 1

30

您的控制容器反转本身不是工厂。您的机箱非常适合工厂模式。

创建一个新的抽象工厂,用于创建你的怪物:

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等等。您的应用程序中的所有其他代码都不会意识到您正在使用不同的怪物(通过使用不同的工厂)。

每次您必须更改混凝土时,您要么切换工厂,要么只修改现有工厂。只要你的怪物实现不违反 Liskovs 替换原则,其他代码都不会受到影响。

工厂与 IoC 容器

那么工厂和 IoC 容器有什么区别呢?IoC 非常擅长解决类的依赖关系并维护生命周期(例如,容器可以在 HTTP 请求结束时自动处理所有一次性用品)。

另一方面,工厂擅长为您创建对象。它做到了这一点,没有别的。

概括

因此,如果您在代码中的某个地方需要获得特定类型的实现,您通常应该使用工厂。工厂本身可以在内部使用 IoC 作为服务定位器(以解决依赖关系)。没关系,因为它是工厂中的实现细节,不会影响应用程序中的任何其他内容。

如果您想解析服务(并且不关心您获得哪个实现,或者您是否获得以前创建的实例),请使用 IoC 容器(通过依赖注入)。

于 2013-04-11T08:14:07.070 回答