我一直认为大型交换机语句是糟糕的OOP设计的症状。在过去,我阅读了讨论这个主题的文章,他们提供了基于OOP的基于OOP的方法,通常基于多态来实例化处理案例的正确对象。
我现在处于一种基于来自TCP套接字的数据流的怪异切换语句的情况,其中协议基本上由换行符结束命令,后跟数据行,后跟结束标记。该命令可以是100个不同命令之一,因此我想找到一种方法将这个怪物切换语句减少到更易于管理的状态。
我已经做了一些谷歌搜索,以找到我记得的解决方案,但遗憾的是,谷歌如今已成为各种查询无关结果的荒地。
这类问题有什么模式吗?有关可能实施的任何建议?
我有一个想法是使用字典查找,将命令文本与要实例化的对象类型相匹配。这具有仅仅创建新对象并在表中为任何新命令插入新命令/类型的优点。
但是,这也存在爆炸类型的问题。我现在需要100个新类,而且我必须找到一种方法将它们干净地连接到数据模型。 “一个真正的转换声明”真的是要走的路吗?
我很感激你的想法,意见或评论。
答案 0 :(得分:33)
您可以从Command Pattern获得一些好处。
对于OOP,如果行为变化足够小,你可能能够将几个类似的命令折叠成一个类,以避免完全的类爆炸(是的,我可以听到OOP大师已经对此进行尖叫)。但是,如果系统已经是OOP,并且100多个命令中的每一个都是真正唯一的,那么只需使它们成为唯一的类并利用继承来巩固常见的东西。
如果系统不是OOP,那么我就不会为此添加OOP ...您可以轻松地使用命令模式和简单的字典查找和函数指针,甚至可以根据命令名称动态生成函数调用,取决于语言。然后,您可以将逻辑关联的函数分组到库中,这些库表示类似命令的集合,以实现可管理的分离。我不知道这种实现是否有一个好的术语......我总是认为它是一种“调度程序”风格,基于MVC处理URL的方法。
答案 1 :(得分:24)
我认为将两个 switch语句作为非OO设计的症状,其中switch-on-enum-type可能被替换为提供抽象接口的不同实现的多种类型;例如,以下......
switch (eFoo)
{
case Foo.This:
eatThis();
break;
case Foo.That:
eatThat();
break;
}
switch (eFoo)
{
case Foo.This:
drinkThis();
break;
case Foo.That:
drinkThat();
break;
}
......或许应该改写成......
IAbstract
{
void eat();
void drink();
}
class This : IAbstract
{
void eat() { ... }
void drink() { ... }
}
class That : IAbstract
{
void eat() { ... }
void drink() { ... }
}
然而,一个切换语句不是 imo这样一个强烈的指示,即switch语句应该被其他东西替换。
答案 2 :(得分:16)
该命令可以是100个不同命令之一
如果你需要从100个不同的东西中做一个,你就无法避免拥有100路分支。您可以在控制流(switch,if-elseif ^ 100)或数据(从字符串到命令/工厂/策略的100元素映射)中对其进行编码。但它会在那里。
您可以尝试将100路分支的结果与不需要知道该结果的事物隔离开来。也许只有100种不同的方法很好;如果这会使代码变得笨拙,就不需要发明你不需要的对象。
答案 3 :(得分:3)
我认为这是少数情况之一,除非有其他解决方案出现,否则大型交换机是最佳答案。
答案 4 :(得分:2)
我看到了策略模式。如果我有100种不同的策略......那就这样吧。巨大的转换声明是丑陋的。所有命令都是有效的类名吗?如果是这样,只需使用命令名作为类名,并使用Activator.CreateInstance创建策略对象。
答案 5 :(得分:2)
在谈论大型switch语句时,有两件事情会浮现在脑海中:
另一方面,地图实现可以符合OCP,并且可以执行O(1)。
答案 6 :(得分:1)
我会说问题不是大转换语句,而是包含在其中的代码的扩散,以及滥用错误范围的变量。
我自己在一个项目中经历过这个,当越来越多的代码进入交换机,直到它变得不可维护。我的解决方案是在参数类上定义,该参数类包含命令的上下文(名称,参数,在切换之前收集的任何内容),为每个case语句创建一个方法,并使用case中的参数对象调用该方法。
当然,一个完全OOP命令调度程序(基于魔法,如反射或Java Activation等机制)更漂亮,但有时你只想解决问题并完成工作;)
答案 7 :(得分:1)
您可以使用字典(如果您使用Java编码,则使用哈希映射)(由Steve McConnell称为表驱动开发)。
答案 8 :(得分:0)
我认为你可以改进的一种方法是让你的代码由数据驱动,所以例如每个代码都匹配处理它的东西(函数,对象)。您还可以使用反射来映射表示对象/函数的字符串并在运行时解决它们,但您可能需要进行一些实验来评估性能。
答案 9 :(得分:0)
处理这个特定问题的最佳方法:序列化和协议干净利用是使用IDL并使用switch语句生成编组代码。因为无论你试图使用什么样的模式(原型工厂,命令模式等),你都需要初始化命令id / string和类/函数指针之间的映射,不知怎的,它将比switch语句运行得慢,因为编译器可以对switch语句使用完美的哈希查找。
答案 10 :(得分:0)
是的,我认为大型案例陈述是一个可以改进代码的症状......通常是通过实施更面向对象的方法。例如,如果我发现自己在switch语句中评估类的类型,那几乎总是意味着我可能会使用泛型来消除switch语句。
答案 11 :(得分:0)
您还可以在此处采用语言方法,并在语法中定义具有关联数据的命令。然后,您可以使用生成器工具来解析语言。我为此目的使用了Irony。或者,您可以使用解释器模式。
在我看来,目标不是构建最纯粹的OO模型,而是创建一个灵活,可扩展,可维护和强大的系统。
答案 12 :(得分:0)
我最近有一个类似的问题,有一个巨大的switch语句,我通过最强简单解决方案查找表以及返回的函数或方法摆脱了丑陋的转换你期望的价值。命令模式是很好的解决方案,但我认为有100个类并不好。 所以我有类似的东西:
switch(id)
case 1: DoSomething(url_1) break;
case 2: DoSomething(url_2) break;
..
..
case 100 DoSomething(url_100) break;
我改变了:
string url = GetUrl(id);
DoSomthing(url);
GetUrl可以转到数据库并返回您要查找的网址,也可以是内存中包含100个网址的字典。 我希望在替换巨大的怪异切换语句时,这可以帮助任何人。
答案 13 :(得分:0)
想一想Windows最初是如何在应用程序消息泵中编写的。它很糟糕。使用更多菜单选项,应用程序运行速度会变慢。随着搜索的命令越来越接近switch语句的底部,响应的等待时间越来越长。持有长时间切换语句是不可接受的。我创建了一个AIX守护程序作为POS命令处理程序,它可以处理256个唯一命令,甚至不知道通过TCP / IP接收的请求流中的内容。流的第一个字符是函数数组的索引。未使用的任何索引都设置为默认消息处理程序;记录并说再见。