我最近了解到OOP中的切换语句很糟糕,特别是罗伯特·马丁的“清洁代码”(第37-39页)。
但请考虑这个场景:我正在编写一个游戏服务器,接收来自客户端的消息,其中包含一个表示玩家行为的整数,例如移动,攻击,挑选物品......等等,将有超过30种不同的动作。当我编写代码来处理这些消息时,没有想到我想到的解决方案,它将不得不在某处使用switch。如果不切换语句,我应该使用什么模式?
答案 0 :(得分:31)
开关就像任何其他控制结构一样。有些地方是最好/最干净的解决方案,还有更多地方是完全不合适的地方。它比其他控制结构更容易被滥用。
在OO设计中,通常认为在像你这样的情况下使用从公共消息类继承的不同消息类型/类,然后使用重载方法“自动”区分不同类型。
在像你这样的情况下,你可以使用映射到你的动作代码的枚举,然后将一个属性附加到每个枚举值,这将允许你使用泛型或类型构建来构建不同的Action子类对象,以便重载方法会起作用。
但这真的很痛苦。
评估是否存在设计选项,例如在您的解决方案中可行的枚举。如果没有,只需使用开关。
答案 1 :(得分:18)
'错'切换语句通常是那些切换对象类型(或者可能是另一种设计中的对象类型的东西)。换句话说,硬编码可能更好地处理多态的东西。其他类型的switch语句可能没问题
您需要一个switch语句,但只需要一个。当您收到消息时,调用Factory对象以返回相应Message子类的对象(Move,Attack等),然后调用message-> doit()方法来完成工作。
这意味着如果添加更多消息类型,则只需更改工厂对象。
答案 2 :(得分:16)
想到Strategy
模式。
策略模式旨在提供一种定义算法族的方法,将每个算法封装为一个对象,并使它们可互换。策略模式允许算法独立于使用它们的客户端。
在这种情况下,“算法族”是您不同的行为。
对于switch语句 - 在“清洁代码”中,Robert Martin说他试图将自己限制为每种类型的一个 switch语句。不要完全消除它们。
原因是switch语句不符合OCP。
答案 3 :(得分:4)
我将消息放入一个数组中,然后将该项与解决方案键匹配以显示消息。
答案 4 :(得分:4)
从设计模式的角度来看,您可以使用指定方案的命令模式。 (见http://en.wikipedia.org/wiki/Command_pattern)。
如果您发现自己在OOP范例中反复使用switch语句,则表明您的类可能设计得不好。假设你有一个适当的超级和子类设计和相当数量的多态性。 switch语句背后的逻辑应该由子类处理。
有关如何删除这些switch语句以及介绍正确的子类的更多信息,我建议您阅读Martin Fowler的重构第一章。或者你可以在这里找到类似的幻灯片http://www1.informatik.uni-wuerzburg.de/database/courses/pi2_ss03_dir/RefactoringExampleSlides.pdf。 (幻灯片44)
答案 5 :(得分:4)
IMO switch
语句不是错误,但应尽可能避免。一种解决方案是使用Map
,其中键是命令,值Command
使用execute()
方法。如果您的命令是数字且无间隙,则为List
。
但是,通常,在实现设计模式时,您将使用switch
语句;一个例子是使用Chain of responsibility模式来处理给定任何命令“id”或“value”的命令。 (还提到了Strategy模式。)但是,在您的情况下,您还可以查看Command模式。
基本上,在OOP中,您将尝试使用其他解决方案,而不是依赖于switch
块,这些块使用过程编程范例。但是,何时以及如何使用这些都是您的决定。我个人经常在使用Factory模式等时使用switch
块。
代码组织的定义是:
Collection
API)Math
类...... add
方法将依赖其他方法来执行此操作,并且不会执行该操作本身,因为它不是它的合同。) 因此,如果您的switch
语句执行不同类型的操作,则“违反”该定义;而使用设计模式并不是因为每个操作都是在它自己的类中定义的(它是自己的一组功能)。
答案 6 :(得分:2)
使用命令。将操作包装在一个对象中,让polymorphism为您进行切换。在C ++中(shared_ptr
只是一个指针,或Java术语中的引用。它允许动态调度):
void GameServer::perform_action(shared_ptr<Action> op) {
op->execute();
}
客户端选择要执行的操作,一旦他们这样做,他们就会将该操作本身发送到服务器,以便服务器不需要进行任何解析:
void BlueClient::play() {
shared_ptr<Action> a;
if( should_move() ) a = new Move(this, NORTHWEST);
else if( should_attack() ) a = new Attack(this, EAST);
else a = Wait(this);
server.perform_action(a);
}
答案 7 :(得分:-1)
我不买。这些OOP狂热者似乎拥有无限RAM和惊人性能的机器。显然,使用无限RAM,您不必担心RAM碎片和连续创建和销毁小帮助程序类时的性能影响。用“美丽代码”一书引用一句话 - “计算机科学中的每个问题都可以通过另一个抽象层次来解决”
如果需要,请使用开关。编译器非常擅长为它们生成代码。