簡體   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