简体   繁体   English

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

[英]Design pattern - Client Server - Command pattern

I have a bunch of commands that I need to batch from the client and execute at the server. 我有一堆需要从客户端批处理并在服务器上执行的命令。 These commands are of different types and the contract for the command and the corresponding return types is shared between the client and server via a library. 这些命令具有不同的类型,并且该命令的约定和相应的返回类型通过库在客户端和服务器之间共享。

Client side code is as follows- 客户端代码如下-

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

Server code - 服务器代码-

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())
       }    
       ..................     
   }
}

For each command there exists a unique execute function, that cannot be exposed as part of the client SDK. 对于每个命令,都有一个独特的执行功能,该功能不能作为客户端SDK的一部分公开。 How do I make design changes such that I can get rid of the massive if/else block? 如何进行设计更改,以便摆脱大量的if / else块? There are tons of commands to be supported. 有大量的命令要支持。 I tried applying the command pattern as suggested here - using the command and factory design patterns for executing queued jobs but this requires each command to implement the ICommand interface, which is not possible 我尝试按照此处的建议应用命令模式- 使用命令和工厂设计模式来执行排队的作业,但这需要每个命令都实现ICommand接口,这是不可能的

Is there a better way to design this? 有没有更好的方法来设计这个?

Basically, you need to map CommandNExcecutor type to CommandN type. 基本上,您需要将CommandNExcecutor类型映射到CommandN类型。

1) Use dictionary. 1)使用字典。 This is easiest way: 这是最简单的方法:

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) Use metadata (attributes). 2)使用元数据(属性)。 This fits plugin-based scenarios, when command types can be added dynamically, without rebuilding core functional. 当可以动态添加命令类型而无需重建核心功能时,这适合基于插件的方案。 It could be your own implementation, or existing DI-container implementation (many of them expose metadata APIs). 它可以是您自己的实现,也可以是现有的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 . 更新

If you want to avoid reflection, in first case just replace type parameter for value in dictionary to Func<CommandBase, ICommandExecutor> : 如果要避免反射,在第一种情况下,只需将字典中的值的类型参数替换为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) },
        ...
    };

This will allow you to create executor through delegate instead of reflection: 这将允许您通过委托而不是反射来创建执行程序:

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

The second case can't avoid reflection completely, since you need to get executor types somehow. 第二种情况无法完全避免反思,因为您需要以某种方式获取执行程序类型。 But it can be leaded to case 1 (with dictionary). 但这可以导致情况1(带有字典)。

Make the lazy map : 制作懒惰的map

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

Then, on Lazy<T> initialization, do all reflection work. 然后,在Lazy<T>初始化上,执行所有反射工作。 Since this is static Lazy<T> , you will do this once per app domain. 由于这是static Lazy<T> ,因此您将在每个应用程序域中执行一次此操作。 Calling ConstructorInfo is fast enough. 调用ConstructorInfo足够快。 Hence, you'll get performance hit only once, when handling first command. 因此,在处理第一个命令时,性能只会受到一次打击。

Another options (they all assume Lazy<Dictionary> ) for case 2 are: 情况2的另一个选项(都假定为Lazy<Dictionary> )是:

  • instead of reflecting ConstructorInfo , build Expression<Func<CommandBase, ICommandExecutor>> s using ConstructorInfo , compile them and put delegates into dictionary - this will be the same as delegates for case 1, but with dynamically supported command types; 代替反射ConstructorInfo ,构建Expression<Func<CommandBase, ICommandExecutor>> s,使用ConstructorInfo ,编译它们并把代表到词典-这将是相同的代表为1的情况下,但与动态支持的命令类型;
  • use DI-container, which emits IL to construct dependencies (AFAIK, NInject does this); 使用DI容器,该容器发出IL来构造依赖关系(AFAIK, NInject这样做);
  • emit IL yourself (IMO, this will totally re-invent the wheel). 自己释放IL(IMO,这将完全重新发明轮子)。

And finally, solve this the easiest way, then measure performance, then consider more complex way. 最后,解决此最简单的方法, 然后评估性能, 然后考虑更复杂的方法。 Avoid premature optimization. 避免过早优化。 I don't know anything about you commands nature, but I suspect, that command execution is much longer, than reflecting something (of course, there's a chance, that I'm wrong). 我对您的命令性质一无所知,但我怀疑命令执行比反映某些事情长得多(当然,我有可能错了)。

Hope this helps. 希望这可以帮助。

Try using the strategy pattern. 尝试使用策略模式。 A simple solution is below: 下面是一个简单的解决方案:

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

The final execution might look like 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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM