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

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 将 Service1Service2 注入我的 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 是一个非常丑陋的名称,从技术上讲它描述了正在发生的事情。这是一个坏主意,因为界面应该在功能上描述你想要的东西。但是由于您没有为您的问题提供任何特定领域的上下文,所以这是我能想到的最好的名字 ;-)