我有一堆命令需要从客户端批处理并在服务器上执行。这些命令的类型不同,命令的合同和相应的返回类型通过库在客户端和服务器之间共享。
客户端代码如下 -
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 :(得分: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的委托相同,但具有动态支持的命令类型; 最后,以最简单的方式解决这个问题,然后测量性能,然后考虑更复杂的方式。避免过早优化。我对你命令性质一无所知,但我怀疑,命令执行很多比反映某些东西(当然,有机会,我错了)。
希望这有帮助。
答案 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);
}
}