繁体   English   中英

一个接口与 DI 的多种实现

[英]Multiple implementations for one interface with DI

现在我正在尝试使用 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 工作方式完全错误? 你会如何解决这个问题?

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

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

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替代原则,其他任何代码都不会受到影响。

Factory vs IoC容器

那么工厂和IoC容器之间有什么区别呢? IoC非常适合解析类的依赖关系并维护生命周期(容器可以在HTTP请求结束时自动处理所有一次性用法)。

另一方面,工厂擅长为您创建物体。 它做到了,没有别的。

摘要

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

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

Func<>的多个实现我使用与以前相同的逻辑,接口IMovement ,三个实现,Cat、Dog 和 Human。

我添加了一个枚举,即MovementEnum 由于这种方法使用 DI 来注册一个Func<> ,它根据特定的键返回和接口实现,因此您可以使用其他类型来表示 Enum。

public enum MovementEnum
{
  Cat = 1,
  Dog = 2,
  Human = 3
}

public class Cat : IMovement
{
  public string Walk()
  {
    return “Im a Cat, walking!”;
  }
}
public class Dog : IMovement
{
  public string Walk()
  {
    return “Im a Dog, walking!”;
  }
}
public class Human : IMovement
{
  public string Walk()
  {
    return “Im a human, walking!”;
  }
}

要先注册,您需要像这样注册课程。

builder.Services.AddScoped<Dog>();
builder.Services.AddScoped<Human>();
builder.Services.AddScoped<Cat>();

然后用枚举和接口注册 Func<>。 要在每个 class 之间进行选择,我使用带有枚举的开关。

builder.Services.AddTransient<Func<MovementEnum, IMovement>>(movementProvider => key =>
{
  switch (key)
  {
    case MovimentEnum.Cat:
      return movementProvider.GetService<Cat>();
    case MovimentEnum.Dog:
      return movementProvider.GetService<Dog>();
    case MovimentEnum.Human:
      return movementProvider.GetService<Human>();
    default:
      return null;
  }
});

要在您的 controller 中使用它,请像这样注入 Func<>。

[ApiController]
[Route("[controller]/[action]")]
public class MovementController : ControllerBase
{

    private readonly IMovement _dogMovement;
    private readonly IMovement _catMovement;
    private readonly IMovement _humanMovement;
    public MovementController(Func<MovementEnum, IMovement> serviceResolver)
    {
        _dogMovement = serviceResolver(MovementEnum.Dog);
        _catMovement = serviceResolver(MovementEnum.Cat);
        _humanMovement = serviceResolver(MovementEnum.Human);
    }

    [HttpGet]
    public string GetCat()
    {
        return _catMovement.Walk();
    }

    [HttpGet]
    public string GetDog()
    {
        return _dogMovement.Walk();
    }

    [HttpGet]
    public string GetHuman()
    {
        return _humanMovement.Walk();
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM