为平滑的 cmd 行 arg 处理选择正确的设计模式
Choosing the right design pattern for smooth cmd line arg processing
我的情况如下
我有一个抽象 Command
class,它有一个 exec
方法。我有一系列具体命令 classes 扩展了这个抽象 class.
我在上下文 class 中有一个 CommandFactory
,它根据我解析并发送到工厂的 cmdline args 创建和 returns 一个适当的命令。 (这里没问题,我能够很好地解析 cmdline args)。
但是在命令工厂中,我有一个很大的 if-else-if 列表,与此不同
public Command getCmd(String cmdType){
if(cmdType == null){
return null;
}
if(cmdType.equalsIgnoreCase("CLEAN")){
return new CleanCmd();
} else if(cmdType.equalsIgnoreCase("KILL")){
return new KillCmd();
} else if(cmdType.equalsIgnoreCase("START")){
return new StartCmd();
}
return null;
}
注意:输入参数是一组标志和参数,对于这个问题的范围来说太复杂了。您可以将 equalsIgnoreCase 视为更复杂的东西。
但是我认为这个 if else 构造有点丑陋。我想用更优雅的东西代替它。如果我错了也请纠正我当前的范例也违反了开闭原则因为每次我添加一个新命令我都会修改工厂?
我建议尽可能使用两个概念:
命令模式 就像这个 answer 中描述的那样,并使用一些 依赖注入 框架,比如 spring 或guice.
有了这两个想法,你的代码可能是这样的:
class CommandFactory {
// Using spring.
// This will inject all the classes implementing Command.
@Autowired
List<Command> commandList;
// Mapping the command name to the implementation
Map<String, Command> commandMap;
// Initializing the command map
@PostConstruct
public void buildMap() {
for (Command command : commandList) {
commandMap.put(command.getType(), command);
}
}
public Command getCmd(String cmdType) {
return commandMap.get(cmdType);
}
}
使用此配置,CommandFactory class 遵守开闭原则。无需修改即可添加新命令并按需接受新命令。
我建议使用原型设计模式来代替您正在使用的命令静态工厂(if-then... ladder)。由于要实例化的可能命令本质上是有限的,因此原型是生成命令的最佳模式。原型模式将保留预先填充的命令存储库,并且当客户端请求新命令时,它将从存储库中获取所需的命令原型并提供一个克隆作为具体命令。
做最简单的事情并没有错,直到它不能满足你的需要。
您可以完全按照您已经完成的方式进行操作,但稍微更巧妙一点的方法是使用基于字符串的 switch
语句:
switch(cmdType) {
case "KILL":
return new KillCommand();
case "CLEAN":
return new CleanCommand();
default:
throw new UnknownCommandException("Unknown command: " + cmdType);
}
我注意到你对 null
的特殊处理。我强烈建议您尽量避免在任何地方返回 null。如果你一直这样做,那么很多时候你也不需要检查 null。
如果您的需求变得更加复杂,请考虑 Map<String,Command>
:
private static Map<String,Command> commandMap = new HashMap<>();
map.put("CLEAN", new CleanCommand());
map.put("KILL", new KillCommand());
...等,然后:
public Command getCommand(String cmdString) {
return commandMap.get(cmdString.toUpperCase());
}
请注意,此 returns 每次都是相同的实例 -- 这通常是一件好事,如果您可以确保命令 class 本身是不可变且线程安全的。
如果您每次都需要一个新实例,您有以下选择:
Map<Class<? extends Command>>
并使用 map.get(s).newInstance()
Map<Supplier<? extends Command>>
并使用 map.get(s).get()
Map<SpecificCommandFactory>
并使用 SpecificCommandFactory
中的方法(如果您还没有定义它,我会称之为 CommandFactory
)
使用此模式意味着您可以在需要时使用许多其他技术:
- 使用框架(Spring、Guice 等)使用依赖注入创建地图
- Adding/removing 初始化时的命令(例如来自配置文件)
- Adding/removing 运行时命令
我的情况如下
我有一个抽象 Command
class,它有一个 exec
方法。我有一系列具体命令 classes 扩展了这个抽象 class.
我在上下文 class 中有一个 CommandFactory
,它根据我解析并发送到工厂的 cmdline args 创建和 returns 一个适当的命令。 (这里没问题,我能够很好地解析 cmdline args)。
但是在命令工厂中,我有一个很大的 if-else-if 列表,与此不同
public Command getCmd(String cmdType){
if(cmdType == null){
return null;
}
if(cmdType.equalsIgnoreCase("CLEAN")){
return new CleanCmd();
} else if(cmdType.equalsIgnoreCase("KILL")){
return new KillCmd();
} else if(cmdType.equalsIgnoreCase("START")){
return new StartCmd();
}
return null;
}
注意:输入参数是一组标志和参数,对于这个问题的范围来说太复杂了。您可以将 equalsIgnoreCase 视为更复杂的东西。
但是我认为这个 if else 构造有点丑陋。我想用更优雅的东西代替它。如果我错了也请纠正我当前的范例也违反了开闭原则因为每次我添加一个新命令我都会修改工厂?
我建议尽可能使用两个概念:
命令模式 就像这个 answer 中描述的那样,并使用一些 依赖注入 框架,比如 spring 或guice.
有了这两个想法,你的代码可能是这样的:
class CommandFactory {
// Using spring.
// This will inject all the classes implementing Command.
@Autowired
List<Command> commandList;
// Mapping the command name to the implementation
Map<String, Command> commandMap;
// Initializing the command map
@PostConstruct
public void buildMap() {
for (Command command : commandList) {
commandMap.put(command.getType(), command);
}
}
public Command getCmd(String cmdType) {
return commandMap.get(cmdType);
}
}
使用此配置,CommandFactory class 遵守开闭原则。无需修改即可添加新命令并按需接受新命令。
我建议使用原型设计模式来代替您正在使用的命令静态工厂(if-then... ladder)。由于要实例化的可能命令本质上是有限的,因此原型是生成命令的最佳模式。原型模式将保留预先填充的命令存储库,并且当客户端请求新命令时,它将从存储库中获取所需的命令原型并提供一个克隆作为具体命令。
做最简单的事情并没有错,直到它不能满足你的需要。
您可以完全按照您已经完成的方式进行操作,但稍微更巧妙一点的方法是使用基于字符串的 switch
语句:
switch(cmdType) {
case "KILL":
return new KillCommand();
case "CLEAN":
return new CleanCommand();
default:
throw new UnknownCommandException("Unknown command: " + cmdType);
}
我注意到你对 null
的特殊处理。我强烈建议您尽量避免在任何地方返回 null。如果你一直这样做,那么很多时候你也不需要检查 null。
如果您的需求变得更加复杂,请考虑 Map<String,Command>
:
private static Map<String,Command> commandMap = new HashMap<>();
map.put("CLEAN", new CleanCommand());
map.put("KILL", new KillCommand());
...等,然后:
public Command getCommand(String cmdString) {
return commandMap.get(cmdString.toUpperCase());
}
请注意,此 returns 每次都是相同的实例 -- 这通常是一件好事,如果您可以确保命令 class 本身是不可变且线程安全的。
如果您每次都需要一个新实例,您有以下选择:
Map<Class<? extends Command>>
并使用map.get(s).newInstance()
Map<Supplier<? extends Command>>
并使用map.get(s).get()
Map<SpecificCommandFactory>
并使用SpecificCommandFactory
中的方法(如果您还没有定义它,我会称之为CommandFactory
)
使用此模式意味着您可以在需要时使用许多其他技术:
- 使用框架(Spring、Guice 等)使用依赖注入创建地图
- Adding/removing 初始化时的命令(例如来自配置文件)
- Adding/removing 运行时命令