我的项目中有一个接口,有2个类实现它:
public interface IService
{
int DoWork();
}
public class Service1:IService
{
public int DoWork()
{
return 1;
}
}
public class Service2:IService
{
public int DoWork()
{
return 2;
}
}
我有一个依赖于IService
的命令处理程序:
public CommandHandler1:ICommandHandler<CommandParameter1>
{
IService _service;
public CommandHandler1(IService service)
{
_service = service
}
public void Handle()
{
//do something
_service.DoWork();
//do something else
}
}
public interface ICommandHandler<TCommandParameter>
where TCommandParameter :ICommandParameter
{
void Handle(TCommandParameter parameter);
}
public interface ICommandParameter
{
}
我想根据用户选择将Service1
或Service2
注入我的CommandHandler1
。假设我有一个enum
,用户可以从中选择一个值:
public enum Services
{
Service_One,
Service_Two
}
如果用户选择Service_One
我希望将Service1
注入我的命令处理程序,如果他选择Service_Two
,我希望将Service2
注入命令处理程序。
我知道我可以使用命名实例,然后调用ObjectFactory.GetInstance<IService>().Named("Service1")
例如,但是
有没有办法通过StructureMap
实现这一点并阻止使用Service Locator
模式?
答案 0 :(得分:9)
使用运行时条件阻止构建对象图。应该修复对象图。使用运行时决策来确定通过对象图的路径。
这里你似乎缺少的是一个抽象,它允许将请求委托给正确的IService
实现;我们称之为IServiceDispatcher
:
interface IServiceDispatcher {
int DoWork(Services data);
}
sealed class ServiceDispatcher : IServiceDispatcher {
private readonly IService service1;
private readonly IService service2;
// NOTE: Feel free to inject the container here instead, as long as
// this class is part of your composition root.
public ServiceDispatcher(IService service1, IService service2) {
this.service1 = service1;
this.service2 = service2;
}
public int DoWork(Services data) {
return this.GetService(data).DoWork();
}
private IService GetService(Services data) {
switch (data) {
case Services.Service_One: return this.service1;
case Services.Service_Two: return this.service2;
default: throw new InvalidEnumArgumentException();
}
}
}
现在,您的CommandHandler1
可能依赖于IServiceDispatcher
:
public CommandHandler1 : ICommandHandler<CommandParameter1> {
private readonly IServiceDispatcher serviceDispatcher;
public CommandHandler1(IServiceDispatcher serviceDispatcher) {
this.serviceDispatcher = serviceDispatcher;
}
public void Handle(CommandParameter1 commandParameter) {
//do something
this.serviceDispatcher.DoWork(commandParameter.Service);
//do something else
}
}
请注意,IServiceDispatcher
是一个非常丑陋的名称,在技术上描述了正在发生的事情。这是一个坏主意,因为界面应该在功能上描述你想要的东西。但是,由于您没有为您的问题提供任何特定于域的上下文,这是我能提出的最佳名称; - )
答案 1 :(得分:1)
这可能不是最好的方法,但应该有效。
为每个指定其代表的ServiceTypes
的服务添加一个属性:
public interface IService
{
public ServiceTypes Type { get; }
public int DoWork();
}
在每个类中实现属性:
public class Service1 : IService
{
public ServiceTypes Type { get { return ServiceTypes.Service_One; } }
public void DoWork()
{
return 1;
}
}
然后,在容器中注册服务的所有实现,并将它们注入到处理程序中。从那里,根据命令中的属性选择实现:
container.For<IService>().Use<Service1>("service1");
container.For<IService>().Use<Service2>("service2");
在命令类中添加所需的ServiceType
:
public class Command1
{
// Other command properties
public ServiceTypes Service { get; set; }
}
在命令处理程序中:
public class CommandHandler : ICommandHandler<Command1>
{
private readonly IEnumerable<IService> _services;
public CommandHandler(IService[] services)
{
_servies = services;
}
public void Handle(Command1 command)
{
var service = _services.Single(s => s.Type == command.Service);
service.DoWork();
}
}
答案 2 :(得分:1)
我会创建一个工厂,它引用IContext
并使用它来解决具体的服务依赖。
public interface ICommandFactory
{
Command1 CreateCommand(Services serviceType);
}
public class CommandFactory : ICommandFactory
{
private readonly IContext _context;
public CommandFactory(IContext context)
{
_context = context;
}
public Command1 CreateCommand(Services serviceType)
{
IService service;
switch(serviceType)
{
case Services.Service_One: service = _context.GetInstance<Service1>();
break;
case Services.Service_Two: service = _context.GetInstance<Service2>();
break;
default:
throw new ArgumentOutOfRangeException("serviceType", serviceType, null);
}
return new Command1(service);
}
}
然后,您注册并使用它:
var container = new Container(_ =>
{
_.For<ICommandFactory>().Use(context=>new CommandFactory(context));
});
var factory = container.GetInstance<ICommandFactory>();
var command = factory.CreateCommand(Services.Service_One);
command.Handle();
首先,选择正确服务的责任与命令本身是分开的。它还允许命令在服务本身之上具有不同的依赖关系,只需调用_context.GetInstance<TypeOfDependency>()
。
关于此与服务定位器相同。服务定位器的主要问题是它隐藏了依赖关系。这不是这里的情况,因为调用该命令的那个显式地声明了对CommandFactory
类的依赖。如果为工厂类引入了接口(将其转换为AbstractFactory
模式),那么实现本身可以成为依赖项解析策略的一部分。例如。它将与依赖框架本身位于同一位置。由于这一点,域模型中没有服务定位器(静态或接口)。