IoC - 多个实现支持单个接口

时间:2012-07-29 10:01:25

标签: c# inversion-of-control castle-windsor ioc-container

我想知道为什么.Net IoC容器不能轻松支持单个接口的多个实现!可能是我错了,但就我所见,像Ninject这样的框架使用注释(how?)部分支持这个功能。我不认为像Windsor或简单注入器这样的其他框架有一个简单的机制来支持这种情况。

为什么许多框架不支持这个? AFAIK,使用接口的最重要原因之一是实现松散耦合。如果框架旨在改善松散耦合,不能流利地支持单个接口的多个实现,我不明白为什么!

P.S。当然我理解在运行时会出现解决问题,容器会混淆选择哪种实现,但这是设计中必须考虑的事情,对吧?

7 个答案:

答案 0 :(得分:10)

Unity具有相同的功能

注册命名依赖

    var container = new UnityContainer();
    container.RegisterType<IConnector, Connector>("TestConnector");

按名称解析

    container.Resolve<IConnector>("TestConnector");

同样的方法

    [Dependency("TestConnector")]
    public IConnector Connector { get; set; }

温莎有相同的

class Program
{
    static void Main(string[] args)
    {
        var container = new WindsorContainer()
            .Register(Component.For<IConnector>().ImplementedBy<ConnectorA>().Named("ConnectorA"))
            .Register(Component.For<IConnector>().ImplementedBy<ConnectorB>().Named("ConnectorB"));

        var connectorA = container.Resolve<IConnector>("ConnectorA");
        Console.WriteLine("Connector type: {0}", connectorA.GetType());
        var connectorB = container.Resolve<IConnector>("ConnectorB");
        Console.WriteLine("Connector type: {0}", connectorB.GetType());
        Console.ReadKey();
    }
}

public interface IConnector
{
}

public class ConnectorA : IConnector
{

}

public class ConnectorB : IConnector
{

}

答案 1 :(得分:7)

我建议查看约定优于配置,尤其是基于约定的依赖注入基于上下文的依赖注入。大多数IoC(如果不是全部)都支持这两种方法。当几个实现绑定到一个接口时,您可以找到许多具有不同IoC库的有趣示例,以及它可能有多大用处。

例如确实支持绑定一个接口的多个实现:依赖on context or attributes, by names, and so one

按上下文

关于实现的片段绑定取决于目标类型automaticaly:

Bind<IWarrior>().To<Samurai>().WhenInjectedInto(typeof(OnLandAttack));
Bind<IWarrior>().To<SpecialNinja>().WhenInjectedInto(typeof(AmphibiousAttack));

按名称

在XML或数据库中进行配置时非常有用。同时考虑InNamedScope

Bind<IWeapon>().To<Shuriken>().Named("Strong");
Bind<IWeapon>().To<Dagger>().Named("Weak");

convention

在项目的不同部分使用不同的依赖关系配置。

答案 2 :(得分:6)

你的前提是错的。

Windsor非常高兴地接受同一服务的多个实现的注册。除了GSerjo在Windsor中提到的命名组件解析支持(默认情况下),第一个注册的实现将获胜但您可以在注册替代实现时使用IsDefault()方法覆盖它。有关详细信息,请参阅http://docs.castleproject.org/Windsor.Registering-components-one-by-one.ashx

如果您希望对多个实现中的选择进行更多控制,可以创建一个IHandlerSelector实现来执行此操作。有关详细信息,请参阅http://stw.castleproject.org/Windsor.Handler-Selectors.ashx

答案 3 :(得分:2)

我的容器Griffin.Container支持它。

registrar.RegisterConcrete<OneImplementation>();
registrar.RegisterConcrete<AnotherImplementation>();

获取:

var services = container.Resolve<ITheService>();

但是,您无法获得一个特定的实现。这是一个设计决定。如果必须获得特定的实现,在容器中注册工厂要好得多。请阅读最佳做法部分中的更多here

Griffin.Container可以在github找到:https://github.com/jgauffin/griffin.container

答案 4 :(得分:1)

StructureMap提供以下功能:

For<IMyInterface>().Add<MyInterfaceImpl1>().Named("MyInterfaceImpl1");
For<IUsingInterface>().Add<UsingInterfaceImpl>().Ctor<IMyInterface>().Is(i => i.GetInstance<IMyInterface>("MyInterfaceImpl1"));

答案 5 :(得分:1)

你的问题有点模糊,因为你没有提供一个具体的例子,说明你何时需要这个。在大多数情况下,您的应用程序或设计存在问题,或者您没有遵循DI最佳实践。

所有容器都允许您使用与IEnumerable<ThatInterface>相同的接口注册多个依赖项,即使它们没有对多个实例的深度支持。然而,将服务列表注入其他服务是一种设计气味,最好将此列表隐藏在Composite之后。这隐藏了抽象背后有多个实现的事实,并允许您通过仅更改应用程序中的单个位置来轻松更改这些实现的使用方式。我不相信任何IoC框架都支持为您生成合成,因为没有一种默认的方法来处理包装的实现。你必须自己写这个复合词。但是,由于编写这样的复合实际上非常简单,这证明在框架中没有这样的特性。

如果您想要多个实现,但总是需要返回一个,基于某些配置,总会有这样做的方法。大多数容器允许您在XML配置文件中配置这些依赖项。但即使容器不包含此类功能,也可以手动从配置文件中读取该值并在容器中注册正确的类型。

如果您有一个用于生产的特定接口的实现和另一个用于单元测试的实现,那么您应该只在容器中注册生产实现。您的单元测试应该清除任何DI容器,并且您应该手动创建一个受测试的类,并在其构造函数中注入假依赖项,只需new类型向上。使用DI容器,污染并使测试复杂化。要实现此目的,您需要围绕构造函数注入模式设计此类型。不要在被测服务中调用容器(或容器上的任何其他外观)来检索依赖项。

答案 6 :(得分:0)

如果要在特定条件下访问实现,则可以使用Dictionary。

UC_Login: 用户必须根据身份验证模式(通过数据库或Active Directory)验证其凭据,每种身份验证模式具有不同的业务逻辑。

我的代码: 我有一个名为IAuthService.cs的接口 我有两个名为DatabaseAuthService.cs和ActiveDirectoryAuthService.cs的类,它们都具有依赖于同一接口的相同IsValidCredential(用户)方法。

public interface IAuthService
{
  Task<bool> IsValidCredentialAsync(User user);
}

public class DatabaseAuthService : IAuthService
{
  private readonly IDatabaseAuthRepository _databaseAuthRepository;
  // User IServiceProvider for access to any other interfaces
  // using Microsoft.Extensions.DependencyInjection; using System;
  public DatabaseAuthService(IServiceProvider serviceProvider)
  => _databaseAuthRepository = serviceProvider.GetService<IDatabaseAuthRepository>();

  public async Task<bool> IsValidCredentialAsync(User user)
  {
    // return await _databaseAuthRepository.something...
  }
}

public class LdapAuthService : IAuthService
{
  public LdapAuthService()
  {
  }
  public async Task<bool> IsValidCredentialAsync(User user)
  {
    // something...
  }
}

实施的条件: 我使用AuthenticationAppService类和LoginAsync(LoginDto dto)方法。

public class AuthenticationAppService
{
  private readonly Dictionary<AuthenticationModeEnum, IAuthService> _authProviders =
      new Dictionary<AuthenticationModeEnum, IAuthService>();
  public AuthenticationAppService(IServiceProvider serviceProvider)
  {
    _authProviders.Add(AuthenticationModeEnum.Database, new DatabaseAuthService(serviceProvider));
    _authProviders.Add(AuthenticationModeEnum.ActiveDirectory, new LdapAuthService());
  }

  public Task<bool> LoginAsync(LoginDto dto)
  {
    var user = Mapper.Map<user, LoginDto>(dto);
    return await _authProviders[(AuthenticationModeEnum)dto.AuthMode].IsValidCredentialAsync(user);
  }
}

也许不是话题,但希望对您有所帮助。