在运行时将接口的不同实现注入命令
Inject different implementations of an Interface to a command at runtime
我的项目中有一个接口,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
{
}
我想根据用户 selection 将 Service1
或 Service2
注入我的 CommandHandler1
。假设我有一个 enum
并且用户可以 select 从中得到一个值:
public enum Services
{
Service_One,
Service_Two
}
如果用户 selects Service_One
我想注入 Service1
到我的命令处理程序 如果他 selects Service_Two
我想注入 Service2
到命令处理程序。
我知道我可以使用命名实例,然后调用 ObjectFactory.GetInstance<IService>().Named("Service1")
例如,但是
有什么办法可以通过 StructureMap
来实现这一点并防止使用 Service Locator
模式吗?
这可能不是最好的方法,但应该可行。
向每个服务添加一个 属性,指定它代表的 ServiceTypes
:
public interface IService
{
public ServiceTypes Type { get; }
public int DoWork();
}
在每个 class:
中实现 属性
public class Service1 : IService
{
public ServiceTypes Type { get { return ServiceTypes.Service_One; } }
public void DoWork()
{
return 1;
}
}
然后,在容器中注册您的服务的所有实现,并将它们注入您的处理程序。从那里,select 基于命令 属性 的实现:
container.For<IService>().Use<Service1>("service1");
container.For<IService>().Use<Service2>("service2");
在命令中添加需要的ServiceType
class:
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();
}
}
我会创建一个工厂,引用 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
class 的依赖。如果为工厂 class 引入接口(将其变成 AbstractFactory
模式),那么实现本身就可以成为依赖项解析策略的一部分。例如。它将与依赖框架本身在同一个地方。由于这一点,域模型中没有服务定位器(静态或接口)。
防止使用运行时条件构建对象图。对象图应该是固定的。使用运行时决策来确定通过对象图的路径。
您在这里似乎缺少的是允许将请求委托给正确的 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
是一个非常丑陋的名称,从技术上讲它描述了正在发生的事情。这是一个坏主意,因为界面应该在功能上描述你想要的东西。但是由于您没有为您的问题提供任何特定领域的上下文,所以这是我能想到的最好的名字 ;-)
我的项目中有一个接口,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
{
}
我想根据用户 selection 将 Service1
或 Service2
注入我的 CommandHandler1
。假设我有一个 enum
并且用户可以 select 从中得到一个值:
public enum Services
{
Service_One,
Service_Two
}
如果用户 selects Service_One
我想注入 Service1
到我的命令处理程序 如果他 selects Service_Two
我想注入 Service2
到命令处理程序。
我知道我可以使用命名实例,然后调用 ObjectFactory.GetInstance<IService>().Named("Service1")
例如,但是
有什么办法可以通过 StructureMap
来实现这一点并防止使用 Service Locator
模式吗?
这可能不是最好的方法,但应该可行。
向每个服务添加一个 属性,指定它代表的 ServiceTypes
:
public interface IService
{
public ServiceTypes Type { get; }
public int DoWork();
}
在每个 class:
中实现 属性public class Service1 : IService
{
public ServiceTypes Type { get { return ServiceTypes.Service_One; } }
public void DoWork()
{
return 1;
}
}
然后,在容器中注册您的服务的所有实现,并将它们注入您的处理程序。从那里,select 基于命令 属性 的实现:
container.For<IService>().Use<Service1>("service1");
container.For<IService>().Use<Service2>("service2");
在命令中添加需要的ServiceType
class:
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();
}
}
我会创建一个工厂,引用 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
class 的依赖。如果为工厂 class 引入接口(将其变成 AbstractFactory
模式),那么实现本身就可以成为依赖项解析策略的一部分。例如。它将与依赖框架本身在同一个地方。由于这一点,域模型中没有服务定位器(静态或接口)。
防止使用运行时条件构建对象图。对象图应该是固定的。使用运行时决策来确定通过对象图的路径。
您在这里似乎缺少的是允许将请求委托给正确的 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
是一个非常丑陋的名称,从技术上讲它描述了正在发生的事情。这是一个坏主意,因为界面应该在功能上描述你想要的东西。但是由于您没有为您的问题提供任何特定领域的上下文,所以这是我能想到的最好的名字 ;-)