2015-01-08 2 views
4

У меня есть интерфейс, каквызовов несколько классов с тем же интерфейсом

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), чтобы каждая конкретная реализация была правильно решена?

ответ

2

Тот факт, что у вас есть несколько адресов-провайдеров является не то, что вызывающий код должен иметь дело с. Поэтому создайте конкретный прокси-провайдер для работы с этими несколькими провайдерами.

Нравится это.

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

Мне нравится этот подход, но как я теперь издеваюсь над моими конкретными провайдерами, чтобы проверить, что AddressProviderProxy вызывает кеш первым, локальный db, если кеш равен null и т. Д.? – friedX

+0

Добавьте тест для него с несколькими издевательствами. Вы можете протестировать логику каждого провайдера отдельно, включая прокси. Логика прокси-сервера заключается в том, что он дает адрес первого провайдера, который дает адрес, поэтому добавьте единичный тест, где первый провайдер дает адрес, добавляет единичный тест, где второй поставщик дает адрес, добавляет единичный тест, где несколько провайдеров предоставляют адрес и т. д. Вам не следует проверять комбинацию ваших фактических провайдеров с прокси-сервером. – Maarten

0

Я не знаком с StructureMap специально, но есть два решения, насколько я могу видеть.

1) Именованные экземпляры. Вы регистрируете свои 3 конкретные реализации IAddressProvider с помощью StructureMap как именованные экземпляры, а затем настраиваете параметры конструктора.

StructureMap конфигурации именованного экземпляра: http://docs.structuremap.net/InstanceExpression.htm#section14

Использование именованных параметров в инъекции конструктора: http://lookonmyworks.co.uk/2011/10/04/using-named-instances-as-constructor-arguments/

2) Дополнительные интерфейсы - Предполагая, что есть только когда-либо будет несколько IAddressProvider реализации и не сотни вы можете создать ICachedAddressProvider, ILocalDbAddressProvider и IExternalAddressProvider, которые реализуют IAddressProvider, а затем используют их в конструкторе потребляющего класса.

Если есть вероятность быть значительно более конкретными IAddressProvider реализаций, тогда вы, возможно, захотите изучить что-то вдоль линий абстрактной фабрики.

0

Не могли бы вы не просто использовать container.GetAllInstances, как так:

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

Edit:

Я понимаю, что вы имеете в виду прямо сейчас. Если вы используете 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; 
    } 
} 
+0

Я хочу вернуть первый непустой адрес, чтобы у меня не было расходов на посещение каждого провайдера (так что сначала кеш, локальный db, если нет кеша, внешнего провайдера, если нет локального db запись) – friedX

+0

Я обновил свой ответ, надеюсь, это будет немного более полезно. –

+0

Это можно сделать без необходимости * зависимости IContainer *.SM поддерживает инъекции коллекций, поэтому вы можете просто добавить зависимость в конструкторе: 'public AddressProviderBuilder (IAddressProvider [] providers)' и вы получите все зарегистрированные реализации * IAddressProvider *. – LetMeCodeThis

Смежные вопросы