17

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

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

PS 当然我知道在运行时会有一个解决问题,容器会混淆选择哪个实现,但这是设计中必须考虑的事情,对吧?

4

7 回答 7

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
{

}
于 2012-07-29T10:29:34.233 回答
8

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

例如确实支持绑定一个接口的多个实现:依赖于上下文或属性,通过名称,等等

按上下文

以下代码片段绑定在实现上自动取决于目标类型:

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");

按照惯例

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

于 2012-07-29T16:47:56.420 回答
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了解更多详情。

于 2012-07-30T00:09:53.663 回答
2

我的容器 Griffin.Container 支持它。

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

并获取:

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

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

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

于 2012-07-29T10:35:46.063 回答
1

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

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

如果你想有多个实现,但总是需要返回一个,基于一些配置,总有办法做到这一点。大多数容器允许您在 XML 配置文件中配置这些依赖项。但是即使容器不包含这样的特性,手动从配置文件中读取这个值并在容器中注册正确的类型也很容易。

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

于 2012-07-30T16:56:17.313 回答
1

StructureMap 提供以下功能:

For<IMyInterface>().Add<MyInterfaceImpl1>().Named("MyInterfaceImpl1");
For<IUsingInterface>().Add<UsingInterfaceImpl>().Ctor<IMyInterface>().Is(i => i.GetInstance<IMyInterface>("MyInterfaceImpl1"));
于 2012-07-29T10:11:22.597 回答
0

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

UC_Login:用户必须根据身份验证模式(通过数据库或活动目录)验证他们的凭据,每种身份验证模式都有不同的业务逻辑。

我的代码:我有一个名为 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);
  }
}

也许不是主题,但希望它有所帮助。

于 2020-10-16T21:17:28.770 回答