【问题标题】:Design Pattern for invoking a Parser in a callback在回调中调用解析器的设计模式
【发布时间】:2020-02-05 20:55:32
【问题描述】:

我有一个应用程序,当对应用程序执行某个操作时,它会调用回调函数。

在这个回调函数中,我想调用一个解析器(例如一个命令行解析器)。

只有当用户在命令行上使用“quit”命令时,此解析器才会在 while 循环中运行并退出并返回回调。

在 while 循环中,用户始终可以访问命令行。根据命令行上的用户输入,解析器采取独特的行动。例如,它将设置一个变量并像这样进入数据库:

command-line>a 100
command-line>

这里将 DB 中的参数a 设置为 100。

我想使用面向对象的设计对解析器进行建模。在这种情况下我可以使用哪种设计模式?

【问题讨论】:

  • 你确定你需要一些非常花哨的图案吗?我只是让解析器成为一个具有parseCommand(std::string command) 之类的函数的类,然后从从用户输入/命令行读取的 while 循环中调用该类。编辑:哦,你到底想要一个解析器本身的设计,还是只为你的标题所暗示的调用步骤?
  • 听起来像XY problem
  • 在回调中运行命令解释器听起来可能会被锁定。
  • 拥有一个有效的执行回调的 shell 是很自然的,而不是相反。一个简单的输入解析器只读取一整行输入,在空间上拆分它(如果您不必担心引用的字符串,这很简单),并检查第一个子字符串以查找要调用的“命令”,然后调用其对应的函数传递参数向量。
  • 除非您希望 shell 锁定进程,否则也有一些解决方案。例如,将外壳和其余处理拆分为单独的线程。或者您可以轮询标准输入以查看是否有可读取的内容,如果没有,则进行一些处理,否则读取并处理一个命令。

标签: c++ oop design-patterns


【解决方案1】:

首先,大多数设计模式都因引入一流函数而过时。您需要的是一个动作名称调度表,基本上是the Command pattern

假设以下定义:

using Tokens = std::vector<std::string>;
using Action = std::function<void(const Tokens&)>;

Tokens read_and_tokenize_line();
void larger_command(const Tokens&);

std::map<std::string, Action> dispatch_table = {
    {"a", [&](const Tokens& tokens){ my_database->set("a", tokens[1]); }},
    {"larger_command", &larger_command},
};

您可以如下编写核心循环:

while (true) {
    auto tokens = read_and_tokenize_line();
    if (tokens[0] == "quit")
        break;
    if (auto it = dispatch_table.find(tokens[0]); it != dispatch_table.end()) {
        it->second(tokens);
    } else {
        std::cerr << "command " << tokens[0] << " not known" << std::endl;
    }
}

【讨论】:

    【解决方案2】:

    您遇到的主要问题是您的解析器在while 循环中运行,这显然会阻塞您的用户界面。我认为这个问题是您要解决的(未说明的)问题。

    首先,存在不需要在 while 循环中运行的解析器。 bisonpush mode 中时可以生成这样的解析器。通过使用这种解析器,您仍然可以依赖您的主 GUI 事件循环,并在回调中为解析器提供令牌,而不会阻塞。

    然后,如果您正在为自己编写一个递归下降解析器,您可以生成一个解析器,它会在每次需要输入时自行中断并保存其状态。这将非常棘手,因为您必须保存解析器的整个状态,包括它的堆栈。这样做的方法是为所有函数使用一个显式堆栈(或许多堆栈,每个操作数类型一个),并将函数调用重写为 switch 语句中的一个大循环。这是(非常)困难且不可维护的。您可以从阅读this article 开始(处理问题的更简单版本)。

    最后,您可以使用后台线程来托管解析器的 while 循环。使用管道或带有信号量的队列将数据发送到解析器(如果您可以自定义输入法)。这里的问题是这个后台线程将(可能)无法更新 UI,因为大多数框架都不是线程安全的。因此,您需要一种机制将信息从解析器发送回主 UI 线程(类似于 PostMessage

    参考文献

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-27
      • 1970-01-01
      • 2011-08-24
      • 2021-08-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多