【问题标题】:Call multiple classes with the same interface调用同一个接口的多个类
【发布时间】:2015-01-08 10:46:17
【问题描述】:

我有一个类似

的界面
public interface IAddressProvider
{
    string GetAddress(double lat, double long);
}

在我的消费类中,我想循环浏览具体的提供者,直到得到结果,例如(简化):

string address;
address = _cachedAddressProvider.GetAddress(lat, long);
if(address == null)
    address = _localDbAddressProvider.GetAddress(lat, long);
if(address = null)
    address = _externalAddressProvider.GetAddress(lat, long);

return address ?? "no address found";

然后我可以模拟每个提供程序以进行单元测试,将 null 设置为返回值以适当地测试所有代码路径。

如何将接口注入我的消费类(最好使用 StructureMap),以便正确解析每个具体实现?

【问题讨论】:

    标签: c# unit-testing dependency-injection structuremap


    【解决方案1】:

    您有多个地址提供者这一事实不是调用代码必须处理的事情。因此,请创建一个特定的 provider-proxy 来处理这些多个提供者。

    像这样。

    public interface IAddressProvider {
        string GetAddress(double lat, double long);
    }
    
    public class AddressProviderProxy: IAddressProvider {
        public AddressProviderProxy(IAddressProvider[] providers) {
            _providers = providers; // TODO: Add a NULL guard
        }
    
        private readonly IAddressProvider[] _providers;
    
        string IAddressProvider.GetAddress(double lat, double long) {
            foreach (var provider in _providers) {
                string address = provider.GetAddress(lat, long);
                if (address != null)
                    return address;
            }
            return null;
        }
    }
    
    // Wire up using DI
    container.Register<IAddressProvider>(
        () => new AddressProviderProxy(
            new IAddressProvider[3] {
                cachedAddressProvider,
                localDbAddressProvider,
                externalAddressProvider
            }
        )
    );
    
    // Use it
    IAddressProvider provider = ...from the container, injected..
    string address = provider.GetAddress(lat, long) ?? "no address found";
    

    【讨论】:

    • 我喜欢这种方法,但是我现在如何模拟我的具体提供程序来测试 AddressProviderProxy 首先调用缓存,如果缓存为空,则调用本地数据库等?
    • 为它添加一个带有多个模拟的测试。您可以分别测试每个提供者的逻辑,包括代理。代理的逻辑是它产生第一个产生地址的提供者的地址,所以在第一个提供者产生地址的地方添加一个单元测试,在第二个提供者产生一个地址的地方添加一个单元测试,在哪里添加一个单元测试多个提供商会产生一个地址等。您不应测试您的实际提供商与代理的组合。
    【解决方案2】:

    你能不能只使用container.GetAllInstances?,像这样:

    var address = new List<string>();
    foreach (var provider in container.GetAllInstances<IAddressProvider>())
    {
        address.add(provider.GetAddress(lat, long));
    }
    

    编辑:

    我现在明白你的意思了。如果您使用的是 StructureMap 2.x,那么我建议您查看 Conditionally 子句。但是,这有 been removed in version 3 支持创建您自己的构建器类,该构建器类应该负责返回正确的实例。

    例如:

    public class AddressProviderBuilder : IInstanceBuilder
    {
        private readonly IContainer container;
    
        public AddressProviderBuilder(IContainer container)
        {
            this.container = container;
        }
    
        public IAddressProvider Build()
        {
            foreach (var provider in this.container.GetAllInstances<IAddressProvider>())
            {
                if (provider.GetAddress(lat, long) != null)
                {
                    return provider;
                }
            }
    
            return null;
        }
    }
    

    【讨论】:

    • 我想返回第一个非空地址,这样我就没有访问每个提供者的费用(所以先缓存,如果没有缓存,则本地db,如果没有则外部提供者本地数据库条目)
    • 我已经更新了我的答案,希望对您有所帮助。
    • 它甚至可以在不需要 IContainer 依赖的情况下完成。 SM 支持注入集合,因此您只需在构造函数中添加依赖项:public AddressProviderBuilder(IAddressProvider[] providers),您将获得所有已注册的 IAddressProvider 实现。
    【解决方案3】:

    我对 StructureMap 并不特别熟悉,但据我所知有两种解决方案。

    1) 命名实例 - 您使用 StructureMap 将 IAddressProvider 的 3 个具体实现注册为命名实例,然后配置构造函数参数。

    StructureMap 命名实例配置:http://docs.structuremap.net/InstanceExpression.htm#section14

    在构造函数注入中使用命名参数:http://lookonmyworks.co.uk/2011/10/04/using-named-instances-as-constructor-arguments/

    2) 更多接口 - 假设只有几个 IAddressProvider 实现而不是数百个,您可以创建一个实现 IAddressProviderICachedAddressProviderILocalDbAddressProviderIExternalAddressProvider,然后使用这些在消费类的构造函数中。

    如果可能有明显更具体的IAddressProvider 实现,那么您可能想要研究一些类似于抽象工厂的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-23
      • 1970-01-01
      • 2018-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多