views:

2141

answers:

22

I have seen it written in multiple threads/comments on stackoverflow that using switch is just bad OOP style. Personally I disagree with this.

There will be many cases where you cannot add code (i.e. methods) to enum classes 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 itself is 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.

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 switch and 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?

+11  A: 

It's bad OOP style.

Not all problems are best solved with OO. Some you want pattern matching, which switch is the poor man's version of.

Pete Kirkham
+2  A: 

Working around your libraries is also a code smell. You may not have a choice, but that doesn't make it a good practice.

jrockway
+1  A: 

Yes, I'm fed up with people telling you it's bad style.

Edit: This made more sense before the question was fixed.

patros
+1  A: 

Case statements can almost always be replaced with polymorphism.

public class NormalCustomer extends Customer {
    public boolean isInvestible() {
        return getSavingsAccount().getBalance() > 1e6;
    }
}

public class PreferredCustomer extends Customer {
    public boolean isInvestible() {
        return isCeo();
    }
}

This approach will simplify the client code. The client code doesn't have to know the details of how "investibility" is calculated and it no longer has to break the Law of Demeter by digging into the state of the Customer object.

hallidave
the law of demeter is all well and good. But do I want to roll a new customer class because I've just released a new product? I don't think so.
oxbow_lakes
Uh, what?hallidave's code is idomatic and The Right Way To Do Things.
jrockway
I agree with Chris, don't apply the general 'rule' blindly. This is a bad example.
eljenso
@chris: if you want to not have to roll out new classes with new customers, or new products, then the design should be quite different. its not wrong either way, its a matter of deciding what is best for your situation. i.e., comes with experience.
Chii
+2  A: 

I find nothing wrong with using the switch statement in OO code. My only criticism is that I would have made a new method on Customer called IsInvestible which hid this logic. There is 0 wrong with using an switch statement as the internal implementation of this method. As you've said you can't add methods to enum's but you can add more methods to Customer.

In the case you don't have access to the source, I'd say a non-instnance method is fine. An OOP purest would require a brand new object but that seems like it may be overkill in this case.

JaredPar
My example was deliberately chosen so that people would suggest an isInvestible method. To which I would then (hopefully) show situations in which this was horribly inappropriate.
oxbow_lakes
I'm right on with this. I wonder how reactions would be different if the same code was presented as an if/else block.
EnocNRoll
+11  A: 

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.

My definition of switch here also includes if-then-else statements that can easily be rewritten as switch statements.

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.

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.

int dbValue = ...;

switch (dbValue)
{
  case 0: return new DogBehaviour();
  case 1: return new CatBehaviour();
  ...
  default: throw new IllegalArgumentException("cannot convert into behaviour:" + dbValue);  
}

EDIT after 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.

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 Customer itself.

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 not replace 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

Switching on type is bad OOP style, switching on a value is fine.

is itself oversimplified.

And to come back to the starting point: a switch is 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.

eljenso
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
oxbow_lakes
The real world doesn't work like this, OO does. Maybe you don't want an OO design? Fine. Maybe you have an OO design, but it is wrong
eljenso
I agree with eljenso. But, please no more talk of code smell. Code is either beautiful, ugly or something inbetween. Code does not smell.
EnocNRoll
@oxbow_lakes, the real world doesn't work like that precisely because the customer cannot be trusted to decide if he or she qualifies for the loan. Also he or she doesn't know the rules of the bank, and there's no way to "give" them to the customer (and trust is a problem, again). That's why OO modeling (like all development) is a creative process. Certain models are "better" (allow you to do more with less/clearer/more efficient code) than others. Switch is not bad, but when you switch on the SAME THING more than once in your code, that's not DRY, and you should think about OO modeling.
Yar
A: 

Case statements can almost always be replaced with polymorphism

And also

boolean investable = customer.isInvestable();

Since the call to isInvestable is polymorphic, the actual algorithm used to make the call is determined by the type of customer.

I think you are both wrong. 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, and possibly not based on a "category" but where they live, whether they are married, what job sector they work in?

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?

Like I said, I'm not saying switch is always fine - but equally it can be perfectly legitimate. If used well, it can be an extremely clear way of writing application logic.

oxbow_lakes
Not WRONG.. It all depends on implementation details. Don't get defensive when you ask for others' opinions..
Ryan Emerle
Yes - you are right about the impl details. But the point was that the answer blindly assumed something about the problem which was actually not true. I posed the problem expecting such answers - didn't mean to be defensive!
oxbow_lakes
CertainProductCustomer extending or implementing Customer class can have different logic for the isInvestable method as a CertainProduct has probably different Customer set than the OtherProduct, right?
macbirdie
Yes - but why is an investibility decision the responsibility of the customer? It seems insane to me that if I want to add a new product to the system, I would need to go and start adding/modifying customer classes...?
oxbow_lakes
I agree that this is a problem, but that doesn't make the switch approach better. To convince me you need to offer a better alternative.
flodin
Well, in my example, all of the investibility logic is in one place which has access to all of the necessary information to make the decision. In your example, it isn't. You are forced into the situation that investibility is solely a function of the user.
oxbow_lakes
we now get into the essence of programming, which is a clear cut definition of whats normally called "business rules". these rules can change at a moment's notice, with no warning. thus, what is the best way to cater for these rules? polymorphism is one, but it has limitations.
Chii
+2  A: 

Î know where you are coming from. Some languages force you to do this.

String str = getStr();
switch(str) {
case "POST" : this.doPost(); break;
case "GET" : this.doGet(); break;
//and the other http instructions
}

And now what? Sure, there is a nice OOP way to do it:

str.request(this);

Too bad that String cannot be extended and now you're considering writing a HttpInstruction class with 8 subclasses for each HttpInstruction. Honestly, especially when talking about parsers, it is just substantially difficult.

It isn't good OOP, that for sure, but good code is not always ... possible.

Let me disgress for a moment. I'm writing my thesis. I personally don't like the usual setup of recursive functions. You normally have like funcRec(arg1,arg), and func(arg1):=func(funcRec(arg1,0));

so I defined it in my thesis with default arguments. Not everybody knows the concept of a default argument. My thesis uses pseudo-code, but the professor had me change the algorithm to the traditional way, because you don't come across default arguments very often, so don't use them. Don't surprise your reader unnecessarily. I think he's right.

But the result is that now I'm stuck with a function whose sole purpose is to ship around default arguments - which could be prettier.

So, the bottom line is: Truely beautiful programs require excellent libraries, excellent code browsers and tools, bugtrackers of the qualitiy of FogBugz, at least, better more integrated, version management of git quality, and so forth. And, umm, people around you who can use all these things and know how to handle all of these things. And most of all: a beaauuuuutiful language that allows elegant solutions to tricky problems.

So, chances are, you're stuck with Java which makes it difficult to come up with a good replacement for switches in all situations. Self would have an elegant solution. But you're not using Self, and if you were, your colleagues wouldn't be able to read it, so forget that.

And now find a compromise.

It's sad, I know.

nes1983
the example you gave: it is a dispatch based on the value, calling out to discrete, different functions. that is a candidate for something called the visitor pattern. if interested, look it up on wikipedia.
Chii
+1  A: 

And now what? Sure, there is a nice OOP way to do it:

str.request(this);

Too bad that String cannot be extended and now you're considering writing a HttpInstruction class with 8 subclasses for each HttpInstruction. Honestly, especially when talking about parsers, it is just substantially difficult.

Ever try C# extension methods? String can be extended.

In MANY OO languages this is NOT the case.
Ryan Emerle
Especially since this post is tagged with Java
Ryan Emerle
+5  A: 

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.

It depends on what you're trying to do. The point is, however, that you should think twice when using switch as it may be an indication of a bad design.

Ryan Emerle
+11  A: 

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 is a smell in an OO system, and you should be asking why instead of getting offended.

Jim Arnold
+13  A: 

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.

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.

Dave Sherohman
This is an excellent solution. The only caveat to this is if the decision needed some kind of collaborating service class, which would then need to be accessible from every product instance. Let's say the decision is based (also) on some fact which has to be looked up indep'tly of customer/product
oxbow_lakes
@Dave You just rewrote the switch into an if-then-else, which in general doesn't make the original problem (to switch or not) go away.
eljenso
@eljenso: Making a choice for which of two criteria to apply is a problem? Yes, this could be solved using nothing but polymorphism, but at the cost of needing a new subclass for every possible combination of Product and CustomerType. That would be a cure far worse than the original situation.
Dave Sherohman
@Chris: Even if other information from a third source is needed, it seems to me that the decision would be a matter of each Product's policies, so the Policy class (hierarchy) would most likely still be the correct place for the method.
Dave Sherohman
I'd double dispatch using an interface in the enum (if I controlled it) or a wrapper (if I didn't control the enum).
Ran Biron
This is such fun, and the only thing I think I actually like in programming, these days. Unfortuhnately, I once read in a blog that OO doesn't actually help productivity. Let it not be true!
Yar
+44  A: 

I think statements like

Using a switch statement is bad OOP style.

and

Case statements can almost always be replaced with polymorphism.

are oversimplifying. The truth is that case statements that are switching on type are bad OOP style. These are the ones you want to replace with polymorphism. Switching on a value is fine.

Bill the Lizard
I agree, but don't forget that (x instanceof C) can, and often is replaced by a test like (x.getType() == 123) which technically is a switch on a value. So, can you clarify the type/value distinction?
eljenso
x.getType is just bastardising the original answer with "technicalities", stop being naughty.
gbjbaanb
@g A very inconvenient technicality then.
eljenso
@eljenso: gbjbaanb is right. Using a type code with a numeric value is the same as switching on type. If the value just represents the type of the object then what's the difference?
Bill the Lizard
@eljenso: I'm more inclined to call it a inconvenient overloading of terminology. "Type" can, but does not always, refer to something analogous to an OO class. The Lizard is right on the money.
Dave Sherohman
@Dave Agreed. @(all) Still, last attempt maybe: a switch is often used to make decisions based on a single enum, int, char, ..., as opposed to if-then-else where you test ranges and other more exotic conditions. Why would that be? Maybe switch is just often used to branch on types of things?
eljenso
+6  A: 

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.

If any programmer is to be deemed competent, it should be assumed he can follow guidelines and use the tools available with discretion and 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).

Snarfblam
+3  A: 

I view switch statements as a more readable alternative to if/else blocks.

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.

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.

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.

EnocNRoll
+4  A: 

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 pattern or something similar. Read up on "Design Patterns" by the Gang of Four.

If your core objects like Customer stays fixed most of the time but operations change often, then you can define operations as objects.

    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 product as I see it referred to here.

Adam Smith
In order to use the visitor pattern, you need control of the code-base. Also, and this is a personal thing, I find that control flow is more difficult to follow when using visitor
oxbow_lakes
when using visitor pattern, the flow is indeed more difficult to follow, but try not to follow it the same way as you would procedural code, but think in terms of interaction and interface contracts. i.e., if a method says it does this, dont follow it, and just assume it works(unit tests to ensure).
Chii
A: 

"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?"

This springs to mind:

interface Investable
{
    boolean isIvestible(Customer c);
}

class FooInvestible 
    implements Investible
{
    public boolean isInvestible(final Customer c)
    {
        // whatever logic, be it switch or other things
    }
}

The "problem" with the original use of swtich and adding new types of decisions is that you likely wind up with some huge rats nest of code that is impossible to maintain in a sane way. Splitting the decisions up into classes forces the decision making to be split up. Then, even if you use switch, the code is likely to stay saner and maintainable.

TofuBeer
Well, the maintainability would come from exactly that: all investibility code is in one place. This was just an example plucked out of the air, and I would actually use the pattern in the accepted answer in this case. However, there are many other situations where a simple switch is fine, IMHO
oxbow_lakes
+2  A: 

I think whether a switch statement is poor OOP practice depends on where you are using the switch statement.

In a factory method for instance, it might be a very simple alternative to writing complicated and potentially buggy reflection-based code.

In most circumstances though, I think switches are just simply design. Quite often hiding operational complexity in different objects with the same method leads to more understandable and possibly even faster code. For instance, if you have a switch that executes lot then having pre-packaged objects could actually save some CPU cycles.

Jeffrey Cameron
+1  A: 

My issue with switch statements is that in real world application there are rarely switch statements that exist in isolation.

A lot of the code that required refactoring in my company's codebase would have entire classes riddled with multiple switch statements throughout such that you had to know of the existence of every single switch statement.

Ultimately, the cleanest refactoring the entire system into a Strategy pattern with a Factory controlling the creation of the strategies based on the single remaining copy of the switch statement.

Due to time constraints we didn't take it any farther because this served our needs. There was still a big giant switch statement, but there was only one, so adding additional strategies only required ipmlementing the interface and adding the creation step to the master switch statement.

Steve Mitcham
+2  A: 

Data coming in from external sources inherently can't be truly object oriented as you aren't bringing in the code. If it contains cases you're going to have cases. Period.

Beyond that, OOP isn't a silver bullet. There are times it's the answer, there are times it's not.

Loren Pechtel
+1  A: 

First, your goal should not be to achieve "good OO style" but good code. And "good" means at least correct, clear, readable and as simple as possible.

So I will reformulate the quesion to: "Is using switch a sign of bad code?", because that's really what I care about. Now I'll proceed to answer it.

Hm, that's a good question :) Usually, using a switch once is not a sign of bad code. But if you switch on the same thing at several points in your class, it's good to consider an alternative design in which you represent the switch alternatives with child classes - and when you consider that, ask yourself especially if classes created like that would be a specialization of the current class and would have an is-a relationship. If so, this gives more points to using inheritance.

One last comment: "Using [language feature X] at all is bad" is dangerously close to "The language designers were stupid to include [language feature X] in it".

Daniel Daranas
+2  A: 

Robert Martin's article on Open Closed Principle provides 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.

Rather than other suggestions, where isInvestible is made a method on Customer, I would say that Cagtegory should become a fully-fledged class, and used for making these decisions:

boolean investible ;
CustomerCategory category = customer.getCategory();
investible = category.isInvestible(customer);

class PrimeCustomerCategory extends CustomerCategory {
    public boolean isInvestible(Customer customer) {
        return customer.isCeo();
    }
}
toolkit