我最近从Robert Martin的《Clean Code》(第37-39页)中了解到,在面向对象编程中,switch语句是不好的。
但考虑以下场景:我正在编写一个游戏服务器,接收来自客户端的消息,其中包含一个整数,指示玩家的动作,如移动、攻击、拾取物品等,将有30多种不同的动作。当我编写处理这些消息的代码时,无论我想出什么解决方案,都需要在某个地方使用switch。如果不使用switch语句,我应该使用什么设计模式?
我最近从Robert Martin的《Clean Code》(第37-39页)中了解到,在面向对象编程中,switch语句是不好的。
但考虑以下场景:我正在编写一个游戏服务器,接收来自客户端的消息,其中包含一个整数,指示玩家的动作,如移动、攻击、拾取物品等,将有30多种不同的动作。当我编写处理这些消息的代码时,无论我想出什么解决方案,都需要在某个地方使用switch。如果不使用switch语句,我应该使用什么设计模式?
一个开关就像其它控制结构一样。在某些情况下,它是最好/最清晰的解决方案,在许多情况下,它完全不合适。它只是被滥用比其他控制结构更多。
在面向对象设计中,通常认为在像你这样的情况下使用不同的消息类型/类继承自一个公共的消息类,然后使用重载方法来“自动”区分不同类型是更可取的。
在你这种情况下,你可以使用映射到你的操作代码的枚举,然后附加到每个枚举值的属性,这将使你能够使用泛型或类型构建来构建不同的Action子类对象,以便重载方法能够正常工作。
但那真的很麻烦。
评估是否有一个设计选项,如枚举,在你的解决方案中是可行的。如果没有,就使用开关。
PlayerAction
枚举,其中包含一个public static PlayerAction forActionId(int actionId)
方法,该枚举将具有公共的doAction
类型方法。不需要使用switch,只需使用PlayerAction.forActionId(number).doAction(args);
即可。现在,您只需添加另一个操作就可以添加枚举值并在其上实现doAction
方法。注意:我也不喜欢使用“ordinal”,因为很容易按字母顺序重新排序枚举值并破坏使用旧数字的任何内容。 - Shadow Man'坏'的switch语句通常是那些在对象类型上(或者在另一种设计中可能是对象类型的东西)进行切换的语句。换句话说,将某些最好由多态性处理的东西硬编码。其他类型的switch语句可能是可以接受的。
你需要一个switch语句,但只有一个。当你收到消息时,调用一个工厂对象来返回适当的Message子类对象(Move、Attack等),然后调用message->doit()方法来执行工作。
这意味着如果您添加更多消息类型,只有工厂对象需要更改。
Map<Class<?>, Thing>
这样的事情怎么样?这非常类似于“在类上进行切换”,但是这被认为是一个好的实践吗? - YoTengoUnLCD我不相信这些面向对象编程(OOP)的狂热者拥有具备无限RAM和惊人性能的机器。显然,如果你拥有无限RAM,就不必担心连续创建和销毁小型辅助类时会出现的RAM碎片化和性能影响。引用《编写优美代码》一书中的一句话来概括 - “计算机科学中的每个问题都可以通过另一个抽象层次来解决”。
如果需要,可以使用switch语句。编译器在生成代码方面表现得非常好。
从设计模式的角度来看,您可以在给定的场景中使用命令模式 (请参阅 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页)。
switch
语句并不是“坏”的,但如果可能的话应该避免使用。一种解决方案是使用Map
,其中键是命令,值是带有execute()
方法的Command
对象。或者,如果您的命令是数字且没有间隙,则可以使用List
。switch
语句;一个例子是使用责任链模式来处理给定任何命令“id”或“value”的命令。(还提到了策略模式。)但是,在您的情况下,您还可以查看命令模式。switch
块。但是,何时以及如何使用取决于您的决定。我个人在使用工厂模式等时经常使用switch
块。
Collection
API)Math
类...)add
方法将依赖于其他方法来执行此操作,并且不会执行该操作本身,因为它不是它的契约。)switch
语句执行不同类型的操作,则“违反”该定义;而使用设计模式则不会,因为每个操作都在其自己的类中定义(其自己的一组功能)。使用命令。将操作包装在对象中,让多态性为您执行切换。在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);
}