具有附加参数解析的命令模式

时间:2018-02-28 19:10:31

标签: java parsing design-patterns structure

最近我真正专注于设计模式并实施它们来解决不同的问题。今天我正在研究命令模式。

我最终创建了一个界面:

public interface Command {
    public void execute();
}

我有几个具体的实现:

public class PullCommand implements Command {

    public void execute() {
        // logic
    }
}

public class PushCommand implements Command {

    public void execute() {
        // logic
    }
}

还有其他一些命令。

现在..问题是BlockingQueue<Command>使用.take()在不同的线程上运行以检索排队的命令并在它们进入时执行它们(我称之为执行程序< / strong>下面的类)由另一个通过解析用户输入并使用.queue()生成它们的类。到目前为止一切都很好......

关于我的难点在于解析命令(CLI应用程序)。 我把所有这些都放在了HashMap中:

private HashMap<String, Command> commands = new HashMap<String, Command>();
commands.put("pull", new PullCommand());
commands.put("push", new PushCommand());
//etc..

当用户输入一个命令时,语法就是这样,有一件事是肯定的,就是“动作”(拉/推)作为第一个参数,所以我总是可以做commands.get(arguments[0])并检查如果是null,如果是,则该命令无效,如果不是,则我已成功检索到表示该命令的对象。棘手的部分是还有其他参数需要解析,并且对于每个命令,解析它的算法是不同的......显然我可以做的一件事是将arguments[]作为参数放到方法中execute()并最终得到execute(String[] args)但这意味着我必须将参数解析放在命令的execute()方法中,我想避免这种方法有几个原因:

  1. Command的执行发生在使用BlockingQueue的另一个线程上,它执行一个命令,然后执行另一个命令等。我想放在execute()内的逻辑有只有执行命令本身,没有解析或任何例如繁重的任务会减慢执行速度(我确实意识到解析几个args不会搞砸性能那么多......但在这里我正在学习结构设计和方法建立良好的编码习惯和良好的解决方案。任何意思都不是完美的。

  2. 这让我觉得我打破了“命令”模式的一些基本原则。 (即使不是这样,我想想一个更好的解决方法)

  3. 很明显,我不能使用具体命令的构造函数,因为HashMap返回已经初始化的对象。接下来要想到的是在对象中使用另一个方法“处理”(process(String[] args))参数并将私有变量设置为解析的结果,并且<{strong}调用此process(String[] args)方法>生成器类在对命令执行queue()之前,因此解析将最终超出执行者类(线程)和第1点。从上面不会有问题。

    但还有另一个问题..如果用户向应用程序输入大量命令会发生什么情况,应用程序对参数执行.get(args[0])并检索PullCommand,它会使用process(String[] args)设置私有变量,因此命令排队执行者类,等待执行。同时..用户输入另一个命令,再次使用.get(args[0]),它从PullCommand检索HashMap(但PullCommandprocess()相同排队等待执行)并且HashMap将被称为 BEFORE 该命令已被执行者类执行,它会搞砸私有变量。我们最终会在BlockingQueue中使用2个PullCommands记录,第二个从用户的角度来看是正确的(因为他输入了他想要它做的事情而且它就是这样),但第一个将与第二个相同(因为它是同一个对象)并且它不对应于初始参数。

    我想到的另一件事是使用 Factory 类来实现对每个命令的解析并返回相应的Command对象。 这意味着,我需要改变HashMap<String, CommandFactory> commands = new HashMap<String, CommandFactory>(); commands.put("pull", new CommandFactory("pull")); commands.put("pull", new CommandFactory("push")); 的使用方式,而不是命令我必须使用Factory类:

    process()

    并且基于传递给Factory的String,它的{{1}}方法将对该命令使用适当的解析,它将返回相应的Command对象,但这意味着这个类可能会非常大,因为包含所有命令的解析..

    总的来说,这似乎是我唯一的选择,但我非常犹豫,因为从结构的角度来看,我认为我没有很好地解决这个问题。这是处理这种情况的好方法吗?有什么我想念的吗?有什么方法可以重构我的部分代码来缓解它吗?

2 个答案:

答案 0 :(得分:0)

你在想这个。命令模式基本上是“保留所有你需要知道的事情并在以后做的事情”,所以把东西传递给执行代码是可以的。

这样做:

  • 用户输入String[]
  • 第一个字符串是命令“name”(现在使用它)
  • 其余字符串是命令的参数(如果有)
  • 将您的界面更改为public void execute(String[] parameters);
  • 执行,将参数传递给命令对象

答案 1 :(得分:0)

在SO中广泛地提出这样的设计问题通常不是一个好主意。只看到没有近距离请求的downvote有点令人惊讶。

在任何情况下,如果不彻底了解你的问题,很难说什么是“最好的”设计,更不用说我做了什么,我不会称任何东西是最好的。因此,我将坚持使用Builder模式我所说的。

通常,只要构造逻辑太复杂而无法适应构造函数,就会使用构建器模式,或者它必须分为几个阶段。在这种情况下,如果你想要一些极端多样化的命令应该如何取决于行动,那么你会想要一些像这样的建设者:

interface CommandBuilder<T extends Command> {
    void parseArgs(String[] args);
    T build();
}

如果您不打算比当前架构更多地使用这些构建器,那么Generic在这里是可选的,否则在类型中更精确是有益的。 parseArgs负责您所指的那些必要的解析。 build应根据您当前的论点吐出Command的实例。

然后您希望调度员地图如下所示:

HashMap<String, Supplier<? extends CommandBuilder<? extends Command>>> builders = new HashMap<>();
commands.put("pull", () -> new PullBuilder());
commands.put("push", () -> new PushBuilder());
// etc

任何这些构建器都可能具有极其复杂的逻辑,正如您所希望的那样。然后就可以了

CommandBuilder builder = builders.get(args[0]).get();
builder.setArgs(args);
queue.add(builder.build());

通过这种方式,您的Command界面可以专注于它应该做的事情。

请注意,在构建builders地图之后,一切都是静态的,并且突变已经过本地化。我不完全明白你的担忧是什么,但应该通过这样做来解决。

然而,这可能是一种过度的设计,取决于你想做什么。