The Strategy Design Pattern in Ruby
Programming large projects can be a quite tedious projects that needs lots of code refactoring again and again, if the code is not well written from the beginning. A very efficient way to make sure that your code is of the highest standards, is by utilizing design patterns if applicable. A design pattern is actually a way of organizing your code so that it is well written, maintainable and extensible. There are various design patterns fitting lots of different situations. One of my favorites is the Strategy design pattern.
What is the Strategy design pattern ?
In the formal sense, the Strategy pattern is one that defines a family of algorithms, encapsulated each one of them and makes it so that they can be interchanged. In a more informal and more understandable definition, it’s a neat way to dynamically assign behaviour to a particular client object (class object). Let’s think of an example. Illustrate that we are creating a new game where there is a user that has a first class attribute, which can either be a Ranger, a Fighter, or a Magician. Each class has different skills and, say, different cost to research.
Now, this already creates some questions for us to solve. How will we handle the situation ? Let’s see how one could do it :
1. We could start by having a User class. This class has an attribute named first_class that can be either set as one of the three types (Ranger, Fighter, Magician). Thus, we would see this :
user = User.new user.first_class = 'Ranger' puts user.cost
Now, you may already see the problem in this. The cost function implemented by the User class has serious design flaws. Let’s see its possible implementation :
def cost if self.first_class == 'Ranger' 100 elsif self.first_class == 'Fighter' 200 elsif self.first_class == 'Magician' 130 end end
An important design principle of good code is the OPEN/CLOSED principle. That code must be open for extension, but closed for modification. Think what will happen if we ever need to add a new first_class, say ‘Knight’. We would need to change the cost function and every other function that operates in the same IF-ELSE way. That would be a big nightmare and a big design problem. Moreover, user.cost does not actually mean anything. Even if we create the method so that it’s something like user.first_class_cost, we are actually passing first class behavior to the User class. Not a good thing, this class is probably doing too much in terms of responsibilities. We should really make sure that every class only does as much as needed and nothing more. So, how can we make this code better and more extensible ?
Enter The Strategy Design Pattern
Let’s start by seeing how this code changes when transformed to utilize the Strategy Pattern :
class User attr_accessor :name, :first_class def first_class= klass @first_class = klass.new end def first_class_description @first_class_description = @first_class.description end end class FirstClass def vehicle 'car' end end class Ranger < FirstClass def cost 30 end end class Fighter < FirstClass def cost 20 end end
See the difference now ? The idea is that we still have a User model, but now we act smarter on defining its first_class. Instead of making it a nightmare String based value, we are actually making it a new model. A new User object now expects a new first_class object in order to operate correctly (we can add it to the constructor as well, if needed). Now, in this example, every first_class gets a new vehicle automatically, so that each new first class character gets a ‘car’. Of course, this can be extended as well, using the same pattern, but it just serves as an example in showing how static things can just stay in the main class. Now, a Ranger, Fighter or anything else, inherits FirstClass and defines a new set of algorithms that operate on the base class. Pretty straight forward i guess.
The best thing about this pattern now is that our object does not need to know what it is, in order to present the description or the cost of its class. The behavior is dynamically set. Let’s see the example execution :
user = User.new user.first_class = Ranger puts user.first_class.description puts user.first_class.cost user.first_class = Fighter puts user.first_class_description puts user.first_class.cost
And when we execute the program we get :
A Ranger here 30 A Fighter here 20
As a side note, see that i also define a first_class_description method, which delegates responsibility directly to the first_class model. This is actually what Rails does with the delegate_to method. It’s a good practice and a fully encapsulated way to do things. That’s it with the Strategy Pattern in Ruby, hope that it will help you in designing your implementation.