【问题标题】:Replace giant switch statement with what?用什么替换巨大的 switch 语句?
【发布时间】:2009-03-18 18:38:59
【问题描述】:

我有一个解析一些模板文件的代码,当它找到一个占位符时,它会用一个值替换它。比如:

<html>
<head>
    <title>%title%</title>
</head>
<body bgcolor="%color%">
...etc.

在代码中,解析器找到这些,调用这个函数:

string getContent(const string& name)
{
    if (name == "title")
        return page->getTitle();
    else if (name == "color")
        return getBodyColor();
    ...etc.
}

然后用返回值替换原来的占位符。

在实际情况下,它不是一个虚拟网页,并且可能出现许多 (50+) 个不同的占位符。

我的代码是 C++,但我猜任何语言都存在这个问题。我猜这更多是关于算法和面向对象设计的。唯一重要的是它必须被编译,即使我想要我不能有任何动态/评估代码。

我虽然想实施责任链模式,但似乎不会对这种情况有太大​​改善。

更新:我还担心另一个线程中的this comment。我应该关心它吗?

【问题讨论】:

    标签: c++ templates switch-statement chain-of-responsibility


    【解决方案1】:

    使用将标签名称映射到标签处理程序的字典。

    【讨论】:

    • 如果您的字典可以使用 O(1) 查找(如散列),则特别好。
    • 很好的答案。有点偏瘦,但绝对是一个好方法。 :)
    • @paul 我不认为这里的性能通常是一个问题——结果代码的清晰度更重要
    • @ejames 我一直不明白为什么长答案比短答案更好
    • @Neil Butterworth 有帮助的 cmets 的简短答案实际上只是需要更多点击的长答案... :)
    【解决方案2】:

    你想要replace conditional with polymorphism。大致:

    string getContent(const string& name) {
        myType obj = factory.getObjForName(name);
        obj.doStuff();
    }
    

    doStuff 重载的地方。

    【讨论】:

    • 当然,开关只是被移到其他地方(工厂),它应该在哪里。
    • 您可能希望将工厂与 Neil Butterworth 的地图结合起来,并从某个配置文件中加载实例化逻辑。编译和动态——太棒了。
    • 其实那个模板就是配置文件。用户可以自行更改。
    • 我的意思是“配置名称到行为的映射”(上面列为 myType)。那么就不会涉及到交换机了。
    • Ummmm... 那会是“obj.doStuff()”吗?
    【解决方案3】:

    您考虑过 XSLT 吗?它非常适合这种事情。我开发了一个内容管理系统,它做了完全相同的事情,并发现 XSLT 非常有效。解析器为您做了很多工作。

    更新:Steven 的评论提出了一个重要的观点——如果你决定走 XSLT 路线,你会希望你的模板是有效的 XHTML。 另外-我会为您的替换令牌使用不同的分隔符。不太可能自然发生的事情。我用了#!PLACEHOLDER#!在我的 CMS 中。

    【讨论】:

    • 我认为您认为 HTML 模板将是有效的 XML 是相当乐观的。 :)
    【解决方案4】:

    我将结合 3 个想法:

    1. (来自 Steven Hugig):使用工厂方法为每个选择器获取不同的类。
      • (来自 Neil Butterworth):在工厂内部,使用字典,这样您就可以摆脱大号 switch(){}。
      • (我的):为每个处理程序类添加一个setup() 方法,将其自身(或新的类实例)添加到字典中。

    解释一下:

    • 创建一个具有static 字典的抽象类,以及使用选择器字符串注册实例的方法。
    • 在每个子类上,setup() 方法将自身注册到超类的字典中
    • 工厂方法只不过是一本字典阅读

    【讨论】:

    • +1。我建议去掉单独的 setup() 函数并将其行为转移到构造函数中——这样就不会被遗忘。
    【解决方案5】:

    尝试将模板读入字符串然后执行替换,而不是解析。

    fileContents = fileContents.Replace("%title%", page->getTitle());
    fileContents = fileContents.Replace("%color%", getBodyColor());
    

    【讨论】:

    • 性能下降,但如果不是绝对需要绝对效率,那么为了代码简单性可能值得。 +1
    • 如果“titleValue”变量包含字符串“%color%”,它将无法正常工作。
    • 是的,它也不太安全,但更简单:)
    • 是的,最好让标记稍微复杂一些,比如“[%color%]”。
    • 我仍然不喜欢这个想法,因为它必须在整个字符串中搜索 50 多个模板占位符中的每一个,并且仍然容易出错。请注意,我的应用程序的用户是同时提供数据和模板的用户。
    【解决方案6】:

    作为"Uncle" Bob Martin mentioned in a previous podacast with Joel and Jeff,您想出的几乎所有东西基本上都是在复制大开关语句。

    如果您觉得实施上述选择的解决方案之一感觉更好,那很好。它可能会使您的代码更漂亮,但在幕后,它本质上是等效的。

    重要的是确保您的大 switch 语句只有一个实例。您的 switch 语句或字典应确定哪个类处理此标记,然后应使用多态性处理后续确定。

    【讨论】:

    • 这不是真的(并且是典型的 Martin 提出的废话)。要添加到开关,我需要修改开关的代码 - 我可以在不修改现有代码的情况下添加到字典。
    • 添加到字典仍然是一个代码更改,编译器发现问题的机会更少。它不像字典是从数据中填充的,它将通过代码填充.向 switch 语句添加案例不需要影响任何现有案例...?
    • 理论上,是的,可以从配置文件或数据库表中填充字典,因此无需重新编译即可对其进行修改。实际上,如果您要更改映射,可能是因为您有一个新的处理程序,所以您已经在重新编译了。
    • @john 实际上也是如此 - 您可以使用插件架构,使用 DLL(或类似的)实现,根本不需要访问现有代码库。不需要(或可能)重新编译。
    • @Neil Butterworth:在深刻的哲学意义上,您确实在移动 switch 语句。这一举措可能会改变形式、可扩展性和其他方面,但所发生事情的本质并没有改变。然而,许多替代品比原来的更易于维护/可扩展。
    猜你喜欢
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-07
    • 2021-11-10
    相关资源
    最近更新 更多