5

I have an interface like

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

In my consuming class I want to cycle through the concrete providers until I get a result, like (simplified):

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

I can then mock each provider for unit testing, setting null as the return value to appropriately test all code paths.

How would i inject the interface into my consuming class (preferably using StructureMap) so that each concrete implementation is correctly resolved?

friedX
  • 327
  • 5
  • 12

3 Answers3

4

The fact that you have multiple address-providers is not something the calling code should have to deal with. So create a specific provider-proxy to handle with these multiple providers.

Like this.

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";
Maarten
  • 22,527
  • 3
  • 47
  • 68
  • I like this approach but how would I now mock my concrete providers to test that AddressProviderProxy calls cache first, local db if cache is null, etc? – friedX Jan 12 '15 at 13:45
  • Add a test for it with multiple mocks. You can test the logic each provider separately, including the proxy. The logic of the proxy is that it yields the address of the first provider that yields an address, so add a unit test where the first provider yields an address, add a unit test where the second provider yields an address, add a unit test where multiple providers yield an address, etc. You shouldn't test the combination of your actual providers with the proxy. – Maarten Jan 12 '15 at 14:52
0

I'm not familiar with StructureMap specifically but there are two solutions as far as I can see.

1) Named Instances - You register your 3 concrete implementations of IAddressProvider with StructureMap as named instances, and then configure the constructor parameters.

StructureMap named instance configuration: http://docs.structuremap.net/InstanceExpression.htm#section14

Using named parameters in constructor injection: http://lookonmyworks.co.uk/2011/10/04/using-named-instances-as-constructor-arguments/

2) More Interfaces - Assuming that there's only ever going to be a few IAddressProvider implementations and not hundreds you could create an ICachedAddressProvider, ILocalDbAddressProvider, and IExternalAddressProvider that implement IAddressProvider and then use those in the constructor of the consuming class.

If there are likely to be significantly more concrete IAddressProvider implementations then you might want to look into something along the lines of an Abstract Factory instead.

Maarten
  • 22,527
  • 3
  • 47
  • 68
XVar
  • 436
  • 4
  • 15
0

Could you not just use container.GetAllInstances?, like so:

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

Edit:

I see what you mean now. If you're using StructureMap 2.x then I would recommend looking at the Conditionally clause. However this has been removed in version 3 in favour of creating your own builder class that should be responsible for returning the correct instance.

For example:

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;
    }
}
Joseph Woodward
  • 9,191
  • 5
  • 44
  • 63
  • I want to return the first non-null address so that I don't have the expense of visiting each provider (so cache first, local db if there is no cache, external provider if there is no local db entry) – friedX Jan 09 '15 at 12:17
  • I've updated my answer, hopefully it will be a little more helpful. – Joseph Woodward Jan 09 '15 at 13:45
  • It could be even done without the need of *IContainer* dependency. SM supports injecting collections so you can just add dependency in the constructor: `public AddressProviderBuilder(IAddressProvider[] providers)` and you would get all registered implementations of *IAddressProvider*. – LetMeCodeThis Feb 02 '15 at 16:44