java 有人不同意以下说法:“使用 switch 是糟糕的 OOP 风格”?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/550919/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does anyone disagree with the statement: "using switch is bad OOP style"?
提问by oxbow_lakes
I have seen it written in multiple threads/comments on stackoverflow that using switchis just bad OOP style. Personally I disagree with this.
我已经看到它写在 stackoverflow 上的多个线程/评论中,使用switch只是糟糕的 OOP 风格。我个人不同意这一点。
There will be many cases where you cannot add code (i.e. methods) to enumclasses you want to switch on because you don't control them, perhaps they are in a 3rd-party jar file. There will be other cases where putting the functionality on the enum itselfis a bad idea because it violates some separation-of-concerns considerations, or it is actually a function of something else as well as the enumeration.
在很多情况下,您无法向enum要打开的类添加代码(即方法),因为您无法控制它们,也许它们位于 3rd-party jar 文件中。在其他情况下,将功能放在 enum 本身是一个坏主意,因为它违反了一些关注点分离的考虑,或者它实际上是其他东西的函数以及 enumeration。
Last of all, switches are concise and clear:
最后,开关简洁明了:
boolean investable;
switch (customer.getCategory()) {
case SUB_PRIME:
case MID_PRIME:
investible = customer.getSavingsAccount().getBalance() > 1e6; break;
case PRIME:
investible = customer.isCeo(); break;
}
I'm not defending every use of switchand I'm not saying it's always the way to go. But statements like "Switch is a code smell" are just wrong, in my opinion. Does anyone else agree?
我不是为每一种使用辩护switch,我也不是说它总是要走的路。但在我看来,像“Switch 是一种代码味道”这样的陈述是错误的。还有人同意吗?
采纳答案by Dave Sherohman
Taking your followup:
采取您的后续行动:
What if this is just the "investibility" logic for a customer wishing for a business loan. Perhaps the innvestibility decision of a customer for another product is really quite different ... Also, what if there are new products coming out all the time, each with different investibility decisions and I don't want to be updating my core Customer class every time this happens?
如果这只是希望获得商业贷款的客户的“可投资性”逻辑,该怎么办?也许客户对另一种产品的投资决策真的很不一样......另外,如果一直有新产品问世,每个产品都有不同的投资决策,我不想每次都更新我的核心客户类什么时候会发生这种情况?
and one of your comments:
以及您的评论之一:
I'm not entirely sure about holding logic close to the data on which it operates. The real world doesn't work like this. When I ask for a loan, the bank decides whether I qualify. They don't ask me to decide for myself.
我并不完全确定将逻辑保持在它所操作的数据附近。现实世界不是这样运作的。当我申请贷款时,银行会决定我是否符合条件。他们不要求我自己做决定。
you are right, as far as this goes.
你是对的,就这点而言。
boolean investable = customer.isInvestable();
is not the optimal solution for the flexibility you're talking about. However, the original question didn't mention the existence of a separate Product base class.
不是您所谈论的灵活性的最佳解决方案。但是,最初的问题没有提到单独的 Product 基类的存在。
Given the additional information now available, the best solution would appear to be
鉴于现在可用的附加信息,最好的解决方案似乎是
boolean investable = product.isInvestable(customer);
The investability decision is made (polymorphically!) by the Product, in accordance with your "real world" argument and it also avoids having to make new customer subclasses each time you add a product. The Product can use whatever methods it wants to make that determination based on the Customer's public interface. I'd still question whether there are appropriate additions which could be made to Customer's interface to eliminate the need for switch, but it may still be the least of all evils.
可投资性决定是由产品(多态!)根据您的“现实世界”参数做出的,它还避免了每次添加产品时都必须创建新的客户子类。产品可以使用它想要的任何方法来根据客户的公共接口做出决定。我仍然怀疑是否可以对客户的界面进行适当的添加以消除切换的需要,但这可能仍然是所有弊端中最少的。
In the particular example provided, though, I'd be tempted to do something like:
但是,在提供的特定示例中,我很想执行以下操作:
if (customer.getCategory() < PRIME) {
investable = customer.getSavingsAccount().getBalance() > 1e6;
} else {
investable = customer.isCeo();
}
I find this cleaner and clearer than listing off every possible category in a switch, I suspect it's more likely to reflect the "real world" thought processes ("are they below prime?" vs. "are they sub-prime or mid-prime?"), and it avoids having to revisit this code if a SUPER_PRIME designation is added at some point.
我发现这比在 switch 中列出所有可能的类别更清晰、更清晰,我怀疑它更有可能反映“现实世界”的思维过程(“它们是否低于黄金?”与“它们是次贷还是中贷? ?"),并且如果在某个时候添加了 SUPER_PRIME 指定,则可以避免重新访问此代码。
回答by Bill the Lizard
I think statements like
我认为这样的陈述
Using a switch statement is bad OOP style.
使用 switch 语句是糟糕的 OOP 风格。
and
和
Case statements can almost always be replaced with polymorphism.
Case 语句几乎总是可以用多态代替。
are oversimplifying. The truth is that case statements that are switching on typeare bad OOP style. These are the ones you want to replace with polymorphism. Switching on a valueis fine.
过于简单化了。事实是,切换类型的case 语句是糟糕的 OOP 风格。这些是你想用多态替换的。打开一个值很好。
回答by eljenso
Switches are a code smell when used in pure OO code. This doesn't mean they are wrong by definition, just that you need to think twice about using them. Be extra careful.
在纯 OO 代码中使用时,开关是一种代码气味。这并不意味着它们的定义是错误的,只是您需要在使用它们时三思而后行。要格外小心。
My definition of switch here also includes if-then-else statements that can easily be rewritten as switch statements.
我在这里对 switch 的定义还包括 if-then-else 语句,这些语句可以很容易地重写为 switch 语句。
Switches can be a sign that you are not defining behaviour close to the data on which it operates, and are not taking advantage of subtype polymorphism for example.
开关可能表明您没有定义与其操作的数据接近的行为,并且没有利用例如子类型多态性。
When using an OO language, you are not forced to program in an OO way. So if you choose to use a more functional or object-based style of programming (e.g. using DTOs that only contain data but no behaviour, as opposed to richer domain models) there is nothing wrong with using switches.
使用 OO 语言时,您不会被迫以 OO 方式进行编程。因此,如果您选择使用更函数式或基于对象的编程风格(例如,使用仅包含数据但不包含行为的 DTO,而不是更丰富的域模型),则使用开关并没有错。
Finally, when writing OO programs, switches come in very handy at the "edge" of your OO model, when something enters your OO model from the non-OO outside world and you need to convert this external entity into an OO notion. You best do this as early as possible. For example: an int from a database can be converted into an object using a switch.
最后,在编写 OO 程序时,开关在 OO 模型的“边缘”非常方便,当某些东西从非 OO 外部世界进入您的 OO 模型并且您需要将此外部实体转换为 OO 概念时。你最好尽早这样做。例如:可以使用开关将数据库中的 int 转换为对象。
int dbValue = ...;
switch (dbValue)
{
case 0: return new DogBehaviour();
case 1: return new CatBehaviour();
...
default: throw new IllegalArgumentException("cannot convert into behaviour:" + dbValue);
}
EDITafter reading some of the responses.
阅读一些回复后进行编辑。
Customer.isInvestable: great, polymorphism. But now you are tying this logic to the customer and you need a subclass for each type of customer just to implement the different behaviour. Last time I checked, this is not how inheritance should be used. You would want the type of customer to be a property of Customer, or have a function that can decide the type of a customer.
Customer.isInvestable: 太好了,多态。但是现在您将此逻辑与客户联系起来,并且您需要为每种类型的客户创建一个子类来实现不同的行为。上次我查了一下,这不是应该如何使用继承。您可能希望客户类型是 的属性Customer,或者具有可以决定客户类型的函数。
Double dispatch: polymorphism twice. But your visitor class is essentially still a big switch and it has some of the same problems as explained above.
双分派:多态两次。但是您的访问者类本质上仍然是一个很大的转换,并且它有一些与上面解释的相同的问题。
Besides, following the example of the OP, the polymorphism should be on the category of the customer, not on Customeritself.
此外,按照OP的例子,多态性应该在客户的类别上,而不是在Customer其本身上。
Switching on a value is fine: ok, but switch statements are in the majority of cases used to test on a single int, char, enum, ... value, as opposed to if-then-else where ranges and more exotic conditions can be tested. But if we dispatch on this single value, and it is not at the edge of our OO model as explained above, then it seems switches are often used to dispatch on type, and not on a value. Or: if you can notreplace the conditional logic of an if-then-else by a switch, then you are probably ok, else you are probably not. Therefore I think switches in OOP are code smells, and the statement
打开一个值是好的:好的,但是 switch 语句在大多数情况下用于测试单个int, char, enum, ... 值,而不是 if-then-else 可以测试范围和更奇特的条件。但是,如果我们对这个单一值进行分派,并且它并不像上面解释的那样位于我们的 OO 模型的边缘,那么似乎开关通常用于分派类型,而不是值。或者:如果你不能用 switch 替换 if-then-else 的条件逻辑,那么你可能没问题,否则你可能不是。因此我认为 OOP 中的开关是代码异味,而语句
Switching on type is bad OOP style, switching on a value is fine.
打开类型是糟糕的 OOP 风格,打开一个值很好。
is itself oversimplified.
本身就过于简单化了。
And to come back to the starting point: a switchis not bad, it's just not always very OO. You don't have to use OO to solve your problem. If you do use OOP, then switches are something you need to give extra attention.
回到起点:aswitch还不错,只是并不总是非常面向对象。您不必使用 OO 来解决您的问题。如果您确实使用 OOP,那么您需要特别注意开关。
回答by Pete Kirkham
It's bad OOP style.
这是糟糕的 OOP 风格。
Not all problems are best solved with OO. Some you want pattern matching, which switch is the poor man's version of.
并不是所有的问题都可以用 OO 来最好地解决。有的要模式匹配,哪个switch是穷人版的。
回答by Jim Arnold
If anything, I'm fed up with people describing this style of programming - in which a bunch of getters are added to the "low hanging" types (Customer, Account, Bank) and the useful code is sprayed around the system in "controllers", "helpers" and "utility" classes - as object orientated. Code like this isa smell in an OO system, and you should be asking whyinstead of getting offended.
如果有的话,我已经受够了描述这种编程风格的人——在这种编程风格中,将一堆 getter 添加到“低挂”类型(客户、帐户、银行)中,并将有用的代码喷洒在“控制器”中的系统周围"、"helpers" 和 "utility" 类 - 作为面向对象。像这样的代码在 OO 系统中是一种味道,你应该问为什么而不是被冒犯。
回答by snarf
Sure switches are poor OO, you shouldn't put a return in the middle of a function, magic values are bad, references should never be null, conditional statements must go in {braces}, but these are guidelines. They shouldn't be followed religiously. Maintainability, refactorability, and understandability are all very important, but all second to actually getting the job done. Sometimes we don't have time to be a programming idealist.
当然开关是糟糕的 OO,你不应该在函数中间放一个返回值,魔法值是坏的,引用永远不应该为空,条件语句必须放在 {括号} 中,但这些是指导方针。他们不应该在宗教上被追随。可维护性、可重构性和可理解性都非常重要,但这些都仅次于实际完成工作。有时我们没有时间成为编程理想主义者。
If any programmer is to be deemed competent, it should be assumed he can follow guidelines and use the tools available with discretionand it should be accepted that he will not always make the best decision. He may choose a less-than-optimal route or make a mistake and run into a hard-to-debug problem because he chose a switch when maybe he shouldn't have or passed around too many null pointers. That's life, and he learns from the mistake, because he is competent.
如果任何程序员被认为是有能力的,就应该假设他可以遵循指导方针并谨慎使用可用的工具,并且应该接受他不会总是做出最好的决定。他可能会选择不太理想的路由,或者犯错误并遇到难以调试的问题,因为他选择了一个交换机,而他可能不应该拥有或传递过多的空指针。这就是生活,他从错误中吸取教训,因为他有能力。
I don't religiously follow programming dogma. I consider guidelines in the context of myself as a programmer and apply them as seems reasonable. We shouldn't harp on these sorts of programming practices unless they are fundamental to the problem at hand. If you want to assert your opinion on good programming practices, it's best to do so in a blog or an appropriate forum (such as right here).
我并不虔诚地遵循编程教条。我在自己作为程序员的背景下考虑指导方针,并在合理的情况下应用它们。我们不应该强调这些类型的编程实践,除非它们是手头问题的基础。如果您想对良好的编程实践发表意见,最好在博客或适当的论坛(例如这里)中进行。
回答by toolkit
Robert Martin's article on Open Closed Principleprovides another viewpoint:
罗伯特马丁关于开闭原则的文章提供了另一种观点:
SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION.
软件实体(类、模块、功能等)应该对扩展开放,但对修改关闭。
In your code example, you are effectively switching on the customer 'Category Type'
在您的代码示例中,您有效地打开了客户“类别类型”
boolean investible ;
switch (customer.getCategory()) {
case SUB_PRIME:
case MID_PRIME:
investible = customer.getSavingsAccount().getBalance() > 1e6; break;
case PRIME:
investible = customer.isCeo(); break;
}
In this current climate, new customer categories might be springing up ;-). This means having to open this class, and continually modify it. It might be OK if you only have a single switch statement, but what happens if you want to use similar logic elsewhere.
在当前的环境下,可能会出现新的客户类别;-)。这意味着必须打开这个类,并不断地修改它。如果您只有一个 switch 语句可能没问题,但是如果您想在其他地方使用类似的逻辑会发生什么。
Rather than other suggestions, where isInvestibleis made a method on Customer, I would say that Cagtegory should become a fully-fledged class, and used for making these decisions:
而不是其他建议,在 whereisInvestible上创建一个方法Customer,我会说 Cagtegory 应该成为一个成熟的类,并用于做出这些决定:
boolean investible ;
CustomerCategory category = customer.getCategory();
investible = category.isInvestible(customer);
class PrimeCustomerCategory extends CustomerCategory {
public boolean isInvestible(Customer customer) {
return customer.isCeo();
}
}
回答by Erik Engheim
I do believe switching on type is a code smell. However I share your concerns about separation-of-concerns in code. But those can be solved in many ways that allow you to still use polymorphism, e.g. the visitor patternor something similar. Read up on "Design Patterns"by the Gang of Four.
我确实相信打开类型是一种代码味道。但是,我同意您对代码中关注点分离的担忧。但是这些可以通过许多方式解决,允许您仍然使用多态,例如访问者模式或类似的东西。阅读四人帮的“设计模式”。
If your core objects like Customerstays fixed most of the time but operations change often, then you can define operations as objects.
如果像Customer这样的核心对象大部分时间都保持固定但操作经常变化,那么您可以将操作定义为对象。
interface Operation {
void handlePrimeCustomer(PrimeCustomer customer);
void handleMidPrimeCustomer(MidPrimeCustomer customer);
void handleSubPrimeCustomer(SubPrimeCustomer customer);
};
class InvestibleOperation : public Operation {
void handlePrimeCustomer(PrimeCustomer customer) {
bool investible = customer.isCeo();
}
void handleMidPrimeCustomer(MidPrimeCustomer customer) {
handleSubPrimeCustomer(customer);
}
void handleSubPrimeCustomer(SubPrimeCustomer customer) {
bool investible = customer.getSavingsAccount().getBalance() > 1e6;
}
};
class SubPrimeCustomer : public Customer {
void doOperation(Operation op) {
op.handleSubPrimeCustomer(this);
}
};
class PrimeCustomer : public Customer {
void doOperation(Operation op) {
op.handlePrimeCustomer(this);
}
};
This looks like overkill, but it can easily save you a lot of coding when you need to handle operations as collections. E.g. display all of them in a list and let user select one. If operations are defined as functions you easily end up with a lot of hard coded switch-case logic, multiple places which needs to be update each time you add another operation, or productas I see it referred to here.
这看起来有点矫枉过正,但是当您需要将操作作为集合处理时,它可以轻松地为您节省大量编码。例如,在列表中显示所有这些并让用户选择一个。如果操作被定义为函数,你很容易最终得到很多硬编码的 switch-case 逻辑,每次添加另一个操作时都需要更新多个地方,或者我看到这里提到的产品。
回答by Ryan Emerle
There are cases when you need to make a decision based on several options and polymorphism is overkill (YAGNI). In this case, switch is fine. Switch is just a tool and can be used or abused just as easily as any other tool.
在某些情况下,您需要根据多个选项做出决定,而多态性是矫枉过正的 (YAGNI)。在这种情况下,switch 很好。Switch 只是一种工具,可以像任何其他工具一样容易地使用或滥用。
It depends on what you're trying to do. The point is, however, that you should think twice when using switch as it maybe an indication of a bad design.
这取决于你想要做什么。然而,关键是在使用 switch 时应该三思而后行,因为这可能表明设计不佳。
回答by EnocNRoll - AnandaGopal Pardue
I view switch statements as a more readable alternative to if/else blocks.
我认为 switch 语句是 if/else 块的更具可读性的替代方案。
I find that if you can boil down your logic to a structure that can be evaluated integrally, the code is likely to be providing a level of encapsulation that is required in OOP.
我发现如果您可以将逻辑归结为可以整体评估的结构,那么代码很可能会提供 OOP 所需的封装级别。
At some point real (messy) logic has to be written for a practical program to ship. Java and C# are not strictly OOP languages, given that they inherit from C. If you want to enforce strictly OOP code, then you'll need to use a language that does not provide idioms which violate that mindset. My view is that both Java and C# are intended to be flexible.
在某些时候,必须编写真正的(凌乱的)逻辑才能发布实际程序。Java 和 C# 不是严格的 OOP 语言,因为它们继承自 C。如果您想严格执行 OOP 代码,那么您需要使用不提供违反这种思维方式的习语的语言。我的观点是 Java 和 C# 都旨在变得灵活。
One of the things that made VB6 so successful, oddly enough, is that it was Object-based, not Object-oriented. So, I would say that pragmattic programmers will invariably combine concepts. Switch can also lead to more manageable code, as long as there is decent encapsulation already programmed in.
使 VB6 如此成功的原因之一,奇怪的是,它是基于对象的,而不是面向对象的。所以,我会说务实的程序员总是会结合概念。Switch 还可以生成更易于管理的代码,只要已经编写了合适的封装。

