在运行时将命令的不同实现注入命令

时间:2015-08-31 06:03:02

标签: c# dependency-injection structuremap multiple-instances

我的项目中有一个接口,有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
{
}

我想根据用户选择将Service1Service2注入我的CommandHandler1。假设我有一个enum,用户可以从中选择一个值:

public enum Services
{  
    Service_One,
    Service_Two 
}

如果用户选择Service_One我希望将Service1注入我的命令处理程序,如果他选择Service_Two,我希望将Service2注入命令处理程序。

我知道我可以使用命名实例,然后调用ObjectFactory.GetInstance<IService>().Named("Service1")例如,但是 有没有办法通过StructureMap实现这一点并阻止使用Service Locator模式?

3 个答案:

答案 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模式),那么实现本身可以成为依赖项解析策略的一部分。例如。它将与依赖框架本身位于同一位置。由于这一点,域模型中没有服务定位器(静态或接口)。