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, строить Expression<Func<CommandBase, ICommandExecutor>> с с использованием ConstructorInfo, обобщать их и поместить делегат в словарь - это будет таким же, как делегаты case 1, но с динамически поддерживаемыми командами;
  • использование DI-контейнера, который испускает IL для построения зависимостей (AFAIK, NInject делает это);
  • испустить ИЛ самостоятельно (ИМО, это полностью заново изобрести колесо).

И, наконец, решить эту проблему самым простым способом, затем производительность мера, затем рассмотреть более сложный путь. Избегайте преждевременной оптимизации. Я ничего не знаю о том, что вы командуете природой, но я подозреваю, что выполнение этой команды много дольше, чем что-то отражающее (конечно, есть шанс, что я ошибаюсь).

Надеюсь, это поможет.

+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