Лучший способ использовать StructureMap для реализации шаблона стратегии
Мое веб-приложение имеет некоторые небольшие различия в бизнес-логике и логике представления в зависимости от типа пользователя, который вошел в систему. Кажется, что получение изменений путем внедрения различных конкретных классов на основе типа пользователя хорошо подходит для DI. Поэтому мне интересно, какие функции StructureMap я должен использовать для достижения этой цели (или если я слишком далеко от базы в целях DI).
(Я только что узнал, что профили не способ сделать это, потому что настройка профиля не является операцией для каждого потока: безопасны ли профили StructureMap для потоков?)
РЕДАКТИРОВАТЬ
Это способ пойти по этому поводу?
public class HomeController
{
private ISomeDependancy _someDependancy;
public HomeController(ISomeDependancy someDependancy)
{
_someDependancy = someDependancy;
}
public string GetNameFromDependancy()
{
return _someDependancy.GetName();
}
}
public interface ISomeDependancy
{
string GetName();
}
public class VersionASomeDependancy : ISomeDependancy
{
public string GetName()
{
return "My Name is Version A";
}
}
public class VersionBSomeDependancy : ISomeDependancy
{
public string GetName()
{
return "My Name is Version B";
}
}
public class VersionARegistry : Registry
{
public VersionARegistry()
{
// build up complex graph here
ForRequestedType<ISomeDependancy>().TheDefaultIsConcreteType<VersionASomeDependancy>();
}
}
public class VersionBRegistry : Registry
{
public VersionBRegistry()
{
// build up complex graph here
ForRequestedType<ISomeDependancy>().TheDefaultIsConcreteType<VersionBSomeDependancy>();
}
}
public class ContainerA : Container
{
public ContainerA()
: base(new VersionARegistry())
{
}
}
public class ContainerB : Container
{
public ContainerB()
: base(new VersionBRegistry())
{
}
}
[TestFixture]
public class Harness
{
[Test]
public void ensure_that_versions_load_based_on_named_containers()
{
ObjectFactory.Initialize(c =>
{
c.ForRequestedType<IContainer>().AddInstances(
x =>
{
x.OfConcreteType<ContainerA>().WithName("VersionA");
x.OfConcreteType<ContainerB>().WithName("VersionB");
}).CacheBy(InstanceScope.Singleton);
});
HomeController controller;
IContainer containerForVersionA = ObjectFactory.GetNamedInstance<IContainer>("VersionA");
controller = containerForVersionA.GetInstance<HomeController>();
Assert.That(controller.GetNameFromDependancy(), Is.EqualTo("My Name is Version A"));
IContainer containerForVersionB = ObjectFactory.GetNamedInstance<IContainer>("VersionB");
controller = containerForVersionB.GetInstance<HomeController>();
Assert.That(controller.GetNameFromDependancy(), Is.EqualTo("My Name is Version B"));
}
}
2 ответа
Один из распространенных способов реализовать это, как описал Марк. У вас есть класс, который принимает массив всех конкретных экземпляров (это должен быть массив, чтобы StructureMap вел себя так, как ожидалось), а затем использовал некоторую логику, чтобы выяснить, какой экземпляр использовать.
Пример кода, который вы можете вставить в консольную программу или в модульный тест:
var container = new Container(x => x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.AddAllTypesOf<IDiscountCalculator>();
}));
var strategy = container.GetInstance<IDiscountStrategy>();
Console.WriteLine(strategy.GetDiscount("Regular", 10)); // 0
Console.WriteLine(strategy.GetDiscount("Normal", 10)); // 1
Console.WriteLine(strategy.GetDiscount("Special", 10)); // 5
который зависит от следующих типов:
public interface IDiscountStrategy
{
decimal GetDiscount(string userType, decimal orderTotal);
}
public class DiscountStrategy : IDiscountStrategy
{
private readonly IDiscountCalculator[] _discountCalculators;
public DiscountStrategy(IDiscountCalculator[] discountCalculators)
{
_discountCalculators = discountCalculators;
}
public decimal GetDiscount(string userType, decimal orderTotal)
{
var calculator = _discountCalculators.FirstOrDefault(x => x.AppliesTo(userType));
if (calculator == null) return 0;
return calculator.CalculateDiscount(orderTotal);
}
}
public interface IDiscountCalculator
{
bool AppliesTo(string userType);
decimal CalculateDiscount(decimal orderTotal);
}
public class NormalUserDiscountCalculator : IDiscountCalculator
{
public bool AppliesTo(string userType)
{
return userType == "Normal";
}
public decimal CalculateDiscount(decimal orderTotal)
{
return orderTotal * 0.1m;
}
}
public class SpecialUserDiscountCalculator : IDiscountCalculator
{
public bool AppliesTo(string userType)
{
return userType == "Special";
}
public decimal CalculateDiscount(decimal orderTotal)
{
return orderTotal * 0.5m;
}
}
Я бы сказал, что это не основная цель DI - это подключать и вводить зависимости, какими бы они ни были. Никакая логика приложения не должна быть задействована в подключении компонентов - она должна строго основываться на конфигурации; либо кодом, либо файлом.config. Эта конфигурация распространяется на все приложения, поэтому довольно сложно определить конфигурацию, которая зависит от пользователя.
Тем не менее, то, о чем вы спрашиваете, хорошо сочетается с DI - оно само по себе немного перпендикулярно DI.
Для вашей конкретной цели я бы определил новую зависимость в форме интерфейса или абстрактного базового класса. Это будет стратегия, которая выбирает правильные конкретные типы (те, которые вы хотите изменить) в зависимости от текущего пользователя.
Вы можете использовать DI, чтобы внедрить все доступные конкретные типы в эту Стратегию, которая затем будет иметь метод или свойство, которое возвращает правильный выбор среди этих внедренных сервисов на основе текущего пользователя.
Во всех местах, где вы привыкли зависеть от разных пользовательских сервисов, вы удаляете эти старые зависимости и заменяете их зависимостью от Стратегии.