简体   繁体   English

一个接口与 DI 的多种实现

[英]Multiple implementations for one interface with DI

Right now I'm trying to teach myself the Dependency Injection pattern with the IOC-container from Autofac.现在我正在尝试使用 Autofac 的 IOC 容器自学依赖注入模式。 I've come up with a very simple example, which is presented below.我提出了一个非常简单的示例,如下所示。 Although the example is simple, I fail to get it working properly.虽然这个例子很简单,但我没能让它正常工作。

Here are my classes/interfaces:这是我的类/接口:

Two monsters, both implementing the IMonster interface:两个怪物,都实现了 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!");
  }
}

Then there's my graveyard:然后是我的墓地:

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

And finally my main:最后是我的主要内容:

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

And this is my problem: During runtime, Autofac throws an exception on this line:这是我的问题:在运行时,Autofac 在此行抛出异常:

var vampire = mVampireFactory.Invoke(300);

It seems that the mVampireFactory is actually trying to instantiate a zombie.看起来 mVampireFactory 实际上是在尝试实例化一个僵尸。 Of course this won't work since the zombie's constructor won't take an int.当然这是行不通的,因为僵尸的构造函数不会采用 int。

Is there a simple way to fix this?有没有简单的方法来解决这个问题? Or did I get the way Autofac works completely wrong?还是我的 Autofac 工作方式完全错误? How would you solve this problem?你会如何解决这个问题?

Your inversion of control container is not a factory per se. 您对控制容器的反转本身不是工厂。 Your case is a perfect fit for the factory pattern. 您的案例非常适合工厂模式。

Create a new abstract factory which is used to create your monsters: 创建一个用于创建怪物的新抽象工厂:

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

And then register its implementation in Autofac. 然后在Autofac中注册其实现。

Finally use the factory in your class: 最后在班上使用工厂:

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

You can of course use specific monster factories too if you want. 如果你愿意,你当然也可以使用特定的怪物工厂。 None the less, using interfaces will imho make your code a lot more readable. 尽管如此,使用接口将使您的代码更具可读性。

Update 更新

But how would I implement the factory? 但是我如何实施工厂呢? On the one hand the factory should not use the IOC container to create the monsters, because that's considered evil (degrades the DI pattern to the service locator anti-pattern). 一方面,工厂不应该使用IOC容器来创建怪物,因为这被认为是邪恶的(将DI模式降级为服务定位器反模式)。

I'm getting so tired of hearing that SL is an anti-pattern. 我已经厌倦了听说SL是一种反模式。 It's not. 不是。 As with all patterns, if you use it incorrectly it will give you a disadvantage. 与所有模式一样,如果你使用不正确,它会给你一个劣势。 That applies for ALL patterns. 这适用于所有模式。 http://blog.gauffin.org/2012/09/service-locator-is-not-an-anti-pattern/ http://blog.gauffin.org/2012/09/service-locator-is-not-an-anti-pattern/

But in this case I don't see why you can't create the implementations directly in your factory? 但在这种情况下,我不明白为什么你不能直接在你的工厂创建实现? That's what the factory is for: 这就是工厂的用途:

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

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

It's not more complicated than that. 它并不复杂。

On the other hand the factory should not create the monsters itself, because that would bypass the IOC-container and tightly couple the factory and the monsters. 另一方面,工厂本身不应该制造怪物,因为它会绕过IOC容器并将工厂和怪物紧密地连接在一起。 Or am I on the wrong track again? 还是我又走错了路? ;-) ;-)

It doesn't matter that the factory is tighly coupled to the monster implementations. 工厂与怪物实现紧密耦合并不重要。 Because that's the purpose of the factory: To abstract away the object creation, so that nothing else in your code is aware of the concretes. 因为这是工厂的目的:抽象出对象创建,以便代码中没有任何其他东西能够识别混凝土。

You could create SuperDeluxeMonsterFactory , MonstersForCheapNonPayingUsersFactory etc. All other code in your application wouldn't be aware of that you are using different monsters (by using different factories). 您可以创建SuperDeluxeMonsterFactoryMonstersForCheapNonPayingUsersFactory等。您的应用程序中的所有其他代码都不会意识到您正在使用不同的怪物(通过使用不同的工厂)。

Each time you have to change concretes you either switch factory or you just modify the existing factory. 每次必须更换混凝土时,您可以切换工厂或只修改现有工厂。 No other code will be affected as long as your monster implementations do not violate Liskovs Substitution Principle. 只要您的怪物实施不违反Liskovs替代原则,其他任何代码都不会受到影响。

Factory vs IoC container Factory vs IoC容器

So what's the difference between a factory and a IoC container then? 那么工厂和IoC容器之间有什么区别呢? The IoC is great at resolving dependencies for your classes and maintain the lifetimes (the container can for instance dispose all disposables automatically when a HTTP request ends).. IoC非常适合解析类的依赖关系并维护生命周期(容器可以在HTTP请求结束时自动处理所有一次性用法)。

The factory on the other hand excels at creating objects for you. 另一方面,工厂擅长为您创建物体。 It does that and nothing else. 它做到了,没有别的。

Summary 摘要

So if you somewhere in your code need to get a specific type of an implementation you typically should use a factory. 因此,如果您的代码中某处需要获取特定类型的实现,则通常应使用工厂。 The factory itself CAN use the IoC as a service locator internally (to resolve dependencies). 工厂本身可以在内部使用IoC作为服务定位器(以解决依赖关系)。 That is OK since it's a implementation detail in the factory which do not affect anything else in your application. 这没关系,因为它是工厂中的实现细节,不会影响应用程序中的任何其他内容。

Use the IoC container (through dependency injection) if you want to resolve a service (and do not care which implementation you get, or if you get a previously created instance). 如果要解析服务,请使用IoC容器(通过依赖注入)(并且不关心您获得的实现,或者您是否获得了先前创建的实例)。

Multiple implementations with Func<> I am using the same logic as before, interface IMovement , three implementations, Cat, Dog, and Human. Func<>的多个实现我使用与以前相同的逻辑,接口IMovement ,三个实现,Cat、Dog 和 Human。

I add an Enum, the MovementEnum .我添加了一个枚举,即MovementEnum Because this approach uses DI to register a Func<> which returns and interface implementation depending on a specific key, you can use another type to represent the Enum.由于这种方法使用 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!”;
  }
}

To register first, you need to register the classes like this.要先注册,您需要像这样注册课程。

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

And then register the Func<> with the Enum and the Interface.然后用枚举和接口注册 Func<>。 To choose between each class, I use a switch with the Enum.要在每个 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;
  }
});

To use this in your controller, inject the Func<> like this.要在您的 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