Computer >> Computer tutorials >  >> Programming >> ruby

Stop Using Case Statements in Ruby

Are you using the full power of OOP (Object-Oriented Programming) or are you missing out?

If you are taking decisions based on the type of an object then you are missing out on one important OOP feature: polymorphism.

Type decisions are usually done inside case statements (which are not OO friendly) & in this article you will learn how to write better code by removing them.

Checking For Types

Let me start by showing you an example where we don’t take advantage of polymorphism.

We want to implement the “Rock, Paper, Scissors” game & we have decided to have one Game class and one class for every possible move.

To check for a winner we are going to implement a play method on Game:

class Game
  def self.play(move1, move2)
    return :tie if move1 == move2

    move1.wins_against?(move2)
  end
end

And here is one of the moves (the others follow the same pattern):

class Rock
  def wins_against?(other_move)
    case other_move
    when Paper then false
    when Scissors then true
    end
  end
end

Now we can call the play method with two moves & we will know if the first move wins.

p Game.play(Rock.new, Paper.new)
# false

Ok, this is working, but can we do better? Can we get rid of that ugly case statement?

Polymorphism Instead of Type Checking

Yes! We can get rid of the type-checking case statement by using OOP fundamentals.

The idea is to use the fact that we know the current class to ask the other movement object if it can beat us.

And we are going to use a method name that is specific to our class (for Rock the method name could be: do_you_beat_rock?)

In Ruby, polymorphism is the ability to send any method calls (also know as messages, in OOP parlance) to any object without having to check the object’s class. This is also known as “duck typing”, but I don’t like that term 🙂

In Java (bear with me for a second…), you have something called “interfaces” which allows you to force a set of methods to be implemented by a class at the compiler level.

We don’t have that in Ruby (probably for the better), so you will have to rely on testing.

Let’s see a code example of what this new implementation looks like:

class Rock
  def wins_against?(other_move)
    other_move.do_you_beat_rock?
  end

  def do_you_beat_paper?
    false
  end

  def do_you_beat_scissors?
    true
  end
end

Notice how the case statement is gone. It has been replaced by a single method call & two method definitions.

Update: As some readers have commented, I didn’t notice that the logic is reversed when implementing this pattern. One proposed solution by Daniel P. Clark is to just flip the order in the play method to move2.wins_against?(move1).

Isn’t this a lot cleaner? What do you think?

One More Move!

Now let’s say you want to add a new move. What would you have to change?

Think about it for a minute…

With the case statement approach you have to add a new branch to every move. Notice that you are “forced” to change a perfectly working method to introduce new functionality.

But that’s not an issue with our more OOP oriented version. All we have to do is add new methods. This is the open/closed principle (the “O” in SOLID) in action.

Another option could be to use metaprogramming (with method_missing or define_method).

Would I actually use metaprogramming for this?

Probably not, unless the number of moves is changing constantly, or there is a big number of moves.

There is a cost to metaprogramming, it’s not a silver bullet like some people may believe. You would be trading performance & readability for a bit of extra flexibility.

Conclusion

In this article you learned that you should avoid using case statements to check for class types. Instead, you should take advantage of polymorphism.

This will help you create better code that can be extended by adding new code instead of changing existing code. Now it’s your turn to take action & do some refactoring 🙂

Btw this doesn’t mean I’m advocating to entirely get rid case statements from your toolbox, but you should be wary whenever you find yourself writing one. Make sure that’s the best solution to the problem.

Don’t forget to share this article if you want me to keep writing articles like these!