2010-12-11 80 views
23

我最近得知开关语句在OOP中很糟糕,特别是Robert Martin的“Clean Code”(p37-39)。但是考虑一下这个场景:我正在写一个游戏服务器,接收来自客户端的消息,其中包含一个表示玩家行为的整数,例如移动,攻击,挑选物品等等,将会有超过30个不同的行动。当我编写处理这些消息的代码时,不管我想到什么解决方案,它都必须在某处使用切换。如果没有切换语句,我应该使用什么模式?开关语句不好?

+0

请参阅[Large Switch statements:Bad OOP? (http://stackoverflow.com/questions/505454/large-switch-statements-bad-oop) – 2010-12-11 14:01:04

+0

总是觉得这很有趣,因为明显改变的代码是在switch语句中添加另一个案例,但添加另一个类不是。 ..有时OO纯粹主义者可以得到一点“宗教”... – 2010-12-11 14:04:20

+3

'“认为有害的”认为有害的“。 – delnan 2010-12-11 14:04:26

回答

21

交换机就像任何其他控制结构。有些地方是最好的/最干净的解决方案,还有更多的地方完全不合适。这只是比其他控制结构更多的滥用方式。

在OO设计中,在像你这样的情况下,通常认为使用不同的消息类型/继承自公共消息类的类,然后使用重载方法“自动”区分不同类型。

在像您这样的情况下,您可以使用映射到您的操作代码的枚举,然后将属性附加到每个枚举值,这样您就可以使用泛型或类型构建来构建不同的Action子类对象,重载方法将起作用。

但这是一个真正的痛苦。

评估是否有设计选项,例如您的解决方案中可行的枚举。如果不是,只需使用开关。

+1

我想你的意思是动态多态而不是静态多态(*重载的方法*) – Geek 2013-03-17 16:26:18

+0

@Toby您能否用下面的例子来详细说明这个语句:“然后给每个枚举值附加一个属性,让您使用泛型或类型构建来构建不同的Action子类对象,以便重载方法可以工作。“? – beinghuman 2017-09-14 06:00:11

13

想到Strategy模式。

策略模式旨在提供一种方法来定义一系列算法,将每个算法封装为一个对象,并使它们可以互换。策略模式可以让算法独立于使用它们的客户端。

在这种情况下,“算法家族”是您的不同行为。


至于switch语句 - 在“清洁守则”,罗伯特·马丁说,他试图把自己限制在每个类型一个 switch语句。不完全消除它们。

原因是switch语句不符合OCP

3

我会把这些消息放入一个数组中,然后将该项目与解决方案关键字进行匹配以显示该消息。

+0

并非所有的东西都是OOP。真的很喜欢这个答案。 – grasshopper 2014-02-07 09:57:24

14

'坏'开关语句通常是那些开关对象类型(或者可能是另一种设计中的对象类型)的开关语句。换句话说,硬编码可能更好地被多态处理。其他类型的开关语句可能是好的

您将需要一个switch语句,但只有一个。当你收到消息时,调用一个Factory对象来返回适当的消息子类(Move,Attack等)的对象,然后调用message-> doit()方法来完成这项工作。

这意味着如果添加更多消息类型,只有工厂对象必须更改。

+0

“Map ,Thing”这样的事情怎么样?'这与“做班级切换”非常相似,但是,这是否被认为是一种好习惯? – YoTengoUnLCD 2016-06-20 20:14:33

4

从设计模式的角度来看,您可以针对给定的场景使用命令模式。 (见http://en.wikipedia.org/wiki/Command_pattern)。

如果您发现自己在OOP范例中反复使用switch语句,这表示您的类可能设计不好。假设你有一个适当的super和sub类设计和相当数量的多态性。 switch语句后面的逻辑应该由子类来处理。

有关如何删除这些switch语句并介绍正确的子类的更多信息,我建议您阅读Martin Fowler的第一章“重构”。或者你可以在这里找到类似的幻灯片http://www1.informatik.uni-wuerzburg.de/database/courses/pi2_ss03_dir/RefactoringExampleSlides.pdf。 (Slide 44)

2

IMO switch陈述不是不好,但应尽可能避免。一种解决方案是使用Map,其中键是命令,值Command对象使用​​方法。或者如果您的命令是数字且没有空白,则为List

但是,通常,在实现设计模式时,您会使用switch语句;一个例子是使用Chain of responsibility模式来处理给定任何命令“id”或“value”的命令。 (也提到了Strategy模式。)但是,就您的情况而言,您还可以查看Command模式。

基本上,在OOP中,您将尝试使用除依赖switch块之外的其他解决方案,这些块使用过程式编程范例。但是,什么时候以及如何使用都是您的决定。

  • 封装是一组具有coherant API类(例如::Collection API使用Factory图案等


    代码组织的定义是当我个人经常使用switch块在许多框架中)

  • a类是一组相关功能(例如:Math类...
  • 方法a功能;它应该只做一件事和一件事。 (例如:在列表中添加项目可能需要放大该列表,在这种情况下,add方法将依赖于其他方法来执行此操作,并且不会执行该操作本身,因为它不是合同。)

因此,如果您switch语句执行各种不同的操作,你是“违反”这一定义;而使用设计模式并不是每个操作都在其自己的类中定义的(它是自己的一组功能)。

1

使用命令。将动作包裹在一个对象中,让多态为你做开关。在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); 
} 
1

我不买它。这些OOP狂热分子似乎拥有无限RAM和惊人性能的机器。显然,对于无限RAM,您不必担心内存碎片以及连续创建和销毁小助手类时的性能影响。为了解释“美丽的代码”一书的引用 - “计算机科学中的每一个问题都可以用另一个抽象层次来解决”

如果你需要的话,可以使用开关。编译器非常擅长为它们生成代码。

+2

你应该重构然后优化。反过来这样做没有任何意义。 – Eva 2013-01-27 20:30:20