(Credits: http://www.indigoexcel.com/)
Strategy is a simple, yet powerful, design pattern to have in your toolbox. It’s main power comes from its clever use of delegation. Here’s the GoF definition:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from client that use it.
Let’s assume the following:
You have just written an backup utility that is beloved by sysadmins all over the world.
So far, your most requested feature is to support multiple forms of notifications. Currently, the user is only notified via standard output (i.e. Log file).
You want to support multiple notifications, but yet, you are too busy/lazy to support the multitudes of notification methods (e.g.: Twitter/IRC/XMPP/etc).
So how? Here’s where Strategy comes to the rescue.
In this example, there are 3 classes:
Context
: Think of this as the client class. This is the class that is going to make use of the pattern. In the previous example, this could be theBackupUtility
class.EmailNotifier
/TwitterNotifier
: These classes represent the different notifiers we want to use.
class Context
def initialize(notifier)
@notifier = notifier
end
def notify(message)
@notifier.notify(message)
end
end
class EmailNotifier
def initialize(opts)
@user_name = opts[:user_name]
@password = opts[:password]
end
def notify(message)
GoogleMailer.new.send(message)
end
end
class TwitterNotifier
def initialize(opts)
@handle = opts[:handle]
end
def notify(message)
Twitter::Client.new.tweet("#{message} #{@handle}")
end
end
So now, if we want to send notifications via email:
context = Context.new(EmailNotifier.new(username: 'ben@gmail.com', password: 'secretlol'))
context.notify('Backup completed successfully!')
And if we want Twitter notifications instead:
context.notifier = TwitterNotifier.new(handle: '@bentanweihao')
context.notify('Catastrophic disaster! Prepare for a long night.')
Now, the beautiful thing about the Strategy is that anyone can write custom notifications.
All the implementation has to do is to implement the notify
method, and then set the notifier
attribute of the Context
class to the new notifier.
Delegate, Delegate, Delegate
Delegation is at the heart of the strategy pattern. Most of its magic derives from the deceptively simple notify
method in the Context
class:
def notify(message)
@notifier.notify(message)
end
The Context
class doesn’t care how the notifier
is implemented. It just calls the notify
method with the correct set of parameters.
Open/Close Principle
Strategy is an example of the Open/Closed principle.
The essence of it is:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
Imagine for a moment that our Context
class is compiled and distributed only in binary form. No one else can modify the code except (closed of modification) yourself. How can you then allow others to create extensions to your code?
If you have understood Strategy, this would be a non-issue since you just have to implement the correct interface, assign the correct attributes and you’ll be all set.
Strategy vs. Template Method
Unlike Template method, Strategy doesn’t require inheritance.
Template method modifies part of the algorithm, while Strategy swaps out the entire algorithm with a new one.
In the next post, I’ll cover an extremely useful and common pattern - Observer.
Thanks for reading!
References
Olsen, R. (2008) Design patterns in Ruby. Upper Saddle River, NJ: Addison-Wesley, p.77-93.
Gamma, E. (1995) Design patterns. Reading, Mass.: Addison-Wesley, p.315-323.