设计模式 - 客户端服务器 - 命令模式

时间:2016-12-15 06:34:06

标签: c# design-patterns client-server factory-pattern command-pattern

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

客户端代码如下 -

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接口,这是不可能的

有没有更好的方法来设计它?

2 个答案:

答案 0 :(得分: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();
}

<强>更新

如果你想避免反思,在第一种情况下,只需将字典中的值的type参数替换为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足够快。因此,在处理第一个命令时,您只会遇到一次性能。

案例2的另一个选项(它们都假定Lazy<Dictionary>)是:

  • 而不是反映ConstructorInfo,使用Expression<Func<CommandBase, ICommandExecutor>>构建ConstructorInfo,编译它们并将委托放入字典 - 这将与案例1的委托相同,但具有动态支持的命令类型;
  • 使用DI容器,它发出IL来构建依赖关系(AFAIK,NInject这样做);
  • 自己发射IL(IMO,这将完全重新发明轮子)。

最后,以最简单的方式解决这个问题,然后测量性能,然后考虑更复杂的方式。避免过早优化。我对你命令性质一无所知,但我怀疑,命令执行很多比反映某些东西(当然,有机会,我错了)。

希望这有帮助。

答案 1 :(得分:0)

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

int index = line.indexOf(";");
part1.add(line.substring(0, index);

最终执行可能如下所示:

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);
    }
}