0

我有一堆命令需要从客户端进行批处理并在服务器上执行。这些命令具有不同的类型,命令和相应的返回类型的契约通过库在客户端和服务器之间共享。设计模式 - 客户端服务器 - 命令模式

客户端代码是如下 -

var client = new ClientSDK(); 
client.Add(new Command1()); 
client.Add(new Command2()); 
client.Add(new Command3()); 

// Execute transmits all the commands to the server 
var results = client.Execute(); 

服务器代码 -

List<CommandResult> Execute(List<CommandBase> commands) 
{ 
    List<CommandResult> results = new List<CommandResult>(); 
    foreach(CommandBase command in commands) 
    { 
     if(command.GetType == Command1) 
     { 
      results.Add(new Command1Executor(command).Execute()) 
     } 
     else if(command.GetType == Command2) 
     { 
      results.Add(new Command1Executor(command).Execute()) 
     } 
     else if(command.GetType == Command3) 
     { 
      results.Add(new Command3Executor(command).Execute()) 
     }  
     ..................  
    } 
} 

每个命令存在唯一的执行功能,即不能暴露作为客户端SDK的一部分。我如何进行设计更改,以便我可以摆脱大量的if/else块?有大量的命令需要支持。我尝试应用这里建议的命令模式 - using the command and factory design patterns for executing queued jobs,但这需要每个命令来实现ICommand接口,这是不可能的

有没有更好的方法来设计这个?

+0

把所有的命令执行者(或functons创建它们)在字典中,由命令类型键。这样你就不需要连锁。 – Evk

回答

1

基本上,您需要将CommandNExcecutor类型映射到CommandN类型。

1)使用字典。这是最简单的方法:

private static readonly Dictionary<Type, Type> map = new Dictionary<Type, Type> 
{ 
    { typeof(Command1), typeof(Command1Executor) }, 
    { typeof(Command2), typeof(Command2Executor) }, 
    ... 
}; 

List<CommandResult> Execute(List<CommandBase> commands) 
{ 
    return commands 
     .Select(command => 
     { 
      var executor = Activator.CreateInstance(map[command.GetType], command); 
      return executor.Execute(); 
     }) 
     .ToList(); 
} 

2)使用元数据(属性)。这适合基于插件的场景,可以在不重建核心功能的情况下动态添加命令类型。它可能是您自己的实现,或者是现有的DI容器实现(其中许多实现了元数据API)。

[AttributeUsage(AttributeTargets.Class)] 
public sealed class CommandExecutorAttribute : Attribute 
{ 
    public CommandExecutorAttribute(Type commandType) 
    { 
     CommandType = commandType; 
    } 

    public Type CommandType { get; } 

    // ... 
} 

[CommandExecutor(typeof(Command1))] 
public sealed class Command1Executor : ICommandExecutor 
{ 
    // ... 
} 

List<CommandResult> Execute(List<CommandBase> commands) 
{ 
    return commands 
     .Select(command => 
     { 
      // obtain executor types somehow, e.g. using DI-container or 
      // using reflection; 
      // inspect custom attribute, which matches command type 
      var executorType = .... 

      var executor = Activator.CreateInstance(executorType , command); 
      return executor.Execute(); 
     }) 
     .ToList(); 
} 

UPDATE

如果你想避免反射,在第一种情况下只需更换类型参数在字典中的价值Func<CommandBase, ICommandExecutor>

private static readonly Dictionary<Type, Func<ICommandExecutor>> map = new Dictionary<Type, Func<ICommandExecutor>> 
    { 
     { typeof(Command1), command => new Command1Executor(command) }, 
     { typeof(Command2), command => new Command2Executor(command) }, 
     ... 
    }; 

这将允许您通过委派,而不是反射创建执行人:

var executor = map[command.GetType](command); 

第二种情况不能完全避免反射,因为您需要以某种方式获得执行程序类型。但它可以引导到案例1(用字典)。

让懒惰map

private static readonly Lazy<Dictionary<Type, ConstructorInfo>> map = ... 

然后,在Lazy<T>初始化,完成所有工作反思。由于这是static Lazy<T>,您将为每个应用域执行一次此操作。拨打ConstructorInfo速度够快。因此,在处理第一条命令时,您只会获得一次性能。

另一个选项(他们都承担Lazy<Dictionary>)的情况下,2:

  • ,而不是反映ConstructorInfo,使用ConstructorInfo打造Expression<Func<CommandBase, ICommandExecutor>> S,编译并把代表到字典 - 这将是同代表们情况1,但具有动态支持的命令类型;
  • 使用DI容器,它发射IL来构造依赖关系(AFAIK,NInject这样做);
  • 自己发射IL(IMO,这将完全重新发明车轮)。

最后,解决这个问题最简单的方法,然后衡量性能,然后考虑更复杂的方式。避免过早优化。我对你对自然的命令一无所知,但我怀疑,这个命令的执行时间比反映某些东西的时间要长(当然,这有可能是我错了)。

希望这会有所帮助。

+0

肯定要避免Activator.CreateInstance和Reflection,因为性能是优先考虑的。 – user1542794

+0

@ user1542794:其实,反思并不是答案的要点。但是我已经更新了它,为您提供了一些关于如何加速代码的想法。 – Dennis

+0

感谢丹尼斯,您的解决方案可以非常干净地解决问题。 – user1542794

0

尝试使用策略模式。一个简单的解决方法是如下:

public class CommandStrategy 
{ 
    private static Dictionary<CommandTypes, Action<CommandStrategy>> strategy; 

    public CommandStrategy() 
    { 
     strategy = new Dictionary<CommandTypes, Action<CommandStrategy>>(); 
     strategy.Add(CommandTypes.Command1, one => new Command1Executor().Execute()); 
     strategy.Add(CommandTypes.Command2, two => new Command2Executor().Execute()); 
     strategy.Add(CommandTypes.Command3, two => new Command3Executor().Execute()); 
    } 

    public void Execute(CommandTypes type) 
    { 
     strategy[type].Invoke(this); 
    } 
} 

最终的执行可能是这样的:

CommandStrategy strategy = new CommandStrategy(); 

List<CommandBase> commands = new List<CommandBase>(){ 
        new Command1(), new Command2(), new Command3() }; 

foreach (var item in commands) 
{ 
    CommandTypes type = (CommandTypes)item; 
    strategy.Execute(type); 
} 
+0

我尝试过使用该模式,但遇到了其他问题。一旦我使用NewtonSoft.Json库对这些命令进行序列化,在服务器端就会出现反序列化的问题。尝试一些解决方案,会让你张贴。 – user1542794