用什么替换巨大的开关语句?

17

我有一段代码可以解析一些模板文件,当它找到一个占位符时,会将其替换为一个值。类似于:

<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++,但我想这个问题在任何语言中都存在。我想这更关乎算法和OO设计。唯一重要的是必须编译它,即使我想使用任何动态/eval'd代码也不行。

我考虑过实现责任链模式,但似乎并不能很好地改善情况。

更新:我也关注这篇帖子中的评论。我应该关心吗?

6个回答

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

1
特别是如果您的字典可以使用O(1)查找,如哈希,则效果更佳。 - Paul Tomblin
3
我从未理解为什么长回答被认为比短回答更好。 - anon
最简单的方式是使用map<string, string>。 - Eclipse
否则,您可以选择使用map<string,TagHandler>,其中TagHandler是一个抽象类,您可以从中继承以处理您正在查找的各种模板关键字。在开始时填充映射,然后使用字典查找。 - Eclipse
1
@Neil Butterworth 简短的回答加上有用的评论实际上就是需要更多点击的长回答... :) - Andy Mikula
显示剩余6条评论

5

您想要使用多态性替换条件语句。大致上如下:

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

where doStuff被重载。


3
当然,开关只是被移动到了别处(工厂),那也是它应该在的地方。 - Ed S.
你可能想要将工厂与Neil Butterworth的映射结合起来,并从某个配置文件中加载实例化逻辑。编译动态-- 令人惊奇。 - Steven Huwig
实际上,那个模板就是配置文件。用户可以自行更改它。 - Milan Babuškov
我的意思是“配置名称到行为的映射”(如上所列的myType)。然后就不需要使用switch了。 - Steven Huwig
那应该是 "obj.doStuff()" 吗? - Dan

3
你考虑过使用XSLT吗?它非常适合这种情况。我开发了一个内容管理系统,也是用的XSLT,效果非常好。解析器会帮你完成很多工作。
更新:Steven的评论提出了一个重要观点-如果你决定采用XSLT方法,你需要确保模板是有效的XHTML。 此外,我建议你为替换标记使用不太可能自然出现的不同分隔符。在我的CMS中,我使用#!PLACEHOLDER#!。

我认为你认为HTML模板将是有效的XML是非常乐观的 :) - Steven Huwig

3
我将结合三个想法:
1.(来自Steven Hugig):使用工厂方法为每个选择器获取不同的类。
2. (来自Neil Butterworth):在工厂内部,使用字典来消除大型的switch(){}语句。
3.(我的):为每个处理程序类添加一个setup()方法,将其本身(或新的类实例)添加到字典中。
稍作解释:
1. 创建一个抽象类,其中包含一个静态字典以及向选择器字符串注册实例的方法。 2. 在每个子类中,setup()方法向父类的字典注册自己。 3. 工厂方法只是一个字典读取。

+1. 我建议摆脱独立的setup()函数,并将其行为移动到构造函数中--这样就不会被遗忘。 - j_random_hacker

2

与其解析,不如尝试将模板读入字符串,然后执行替换操作。

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

性能会受到影响,但如果绝对效率不是必须的话,为了代码简洁性而值得。+1 - Brian R. Bondy
1
如果变量"titleValue"包含字符串"%color%",它将无法正常工作。 - Milan Babuškov
是的,这样做可能不太安全,但更简单 :) - Brian R. Bondy
是的,可能最好将令牌稍微复杂一些,比如“[%color%]”。 - Gordon Bell
我仍然不喜欢这个想法,因为它必须在整个字符串中搜索50多个模板占位符之一,并且仍然容易出错。请注意,我的应用程序的用户是提供数据和模板的人。 - Milan Babuškov

2

正如“叔叔”Bob Martin在之前与Joel和Jeff的播客中提到的那样,你所想出的几乎都是重复的大型switch语句。

如果你更喜欢实现上述解决方案之一,那也没问题。这可能会让你的代码更漂亮,但本质上它们是等价的。

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


这是不正确的(并且是马丁常说的废话)。要添加到开关中,我需要修改开关的代码 - 我可以在不修改现有代码的情况下添加到字典中。 - anon
将内容添加到字典中仍然属于代码更改,并且编译器检测问题的机会较少。这不像是从数据中填充字典,而是通过代码填充。向 switch 语句添加 case 不需要影响任何现有的 case...? - Bittercoder
理论上,是的,字典可以从配置文件或数据库表中填充,因此可以在不重新编译的情况下进行修改。实际上,如果您正在更改映射,那可能是因为您有一个新的处理程序,所以您已经在进行重新编译了。 - JohnMcG
@john 实际上也可以 - 您可以使用插件架构,使用 DLL(或类似)实现,根本不需要访问现有的代码库。无需重新编译(或不可能)。 - anon
2
@Neil Butterworth:从深层哲学意义上讲,您确实正在移动switch语句。这种移动可能会改变形式、可扩展性和其他方面,但发生的本质并没有改变。然而,许多替代方案比原始方案更易于维护/扩展。 - Harper Shelby
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接