By definition, the Strategy Pattern defines a family of an algorithm and encapsulate each of them in you own class, that way it will enable that the strategy can be interchanged.
In other words, the pattern allows the main algorithm to vary independently of the clients that use it.
It's very similar to Template Method but it uses a different approach, it prefers to use Composition over Inheritance.
To get a start, imagine that exist a Parser
class that is responsible to parse texts to different formats, initially JSON
and XML
.
class TextParser
attr_accessor :text, :parser
def initialize(text, parser)
@text = text
@parser = parser
end
def parse
if parser == :json
"{ text: #{text} }"
elsif parser == :xml
"<text>#{text}</text>"
end
end
end
parser_xml = TextParser.new('My Text', :xml)
parser_xml.parse => "<text>My Text</text>"
puts "\n -- \n"
parser_json = TextParser.new('My Text', :json)
parser_json.parse => "{ text: My Text }"
The solution seems to work fine, but uses an if
to define which strategy
to format the text. It's not a good approach, for the same reasons that I already explained in Template Method post.
If we need to add a new (or many others) parsers? Are we going to add a lot of ifs
to check each parser? I won't scale and doesn't conform with Open Closed Principle
We need a better approach, that will allow a flexible way to add new parsers, without the need to change the existent code.
Use the right Strategy to contemplate many parsers
The Template Method
, could solve this, but the approach of use Inheritance is not that we want. Inheritance has certain problems:
- Make the code very coupled
- Does not favor encapsulation
The Strategy Pattern will provide us a nice approach that will avoid the problems above and will conform the Open Closed Principle from SOLID (that I plan to talk in a future post).
At first, to solve the problem and give to the code more flexibility, the TextParser#parse
method will only be responsible to delegate the call of parser
method to a given class, each of these concrete classes, (JSONParser or XMLParser) will contain their own behavior to implement the parse
method and will be injected through the TextParser
constructor as a dependency.
class BaseParser
def parse(text)
raise 'Must implement!'
end
end
###
class XMLParser < BaseParser
def parse(text)
"<text>#{text}</text>"
end
end
###
class JSONParser < BaseParser
def parse(text)
"{ text: #{text} }"
end
end
###
class TextParser
attr_reader :text, :parser
def initialize(text, parser)
@text = text
@parser = parser
end
def parse
puts(parser.parse(text))
end
end
parser_xml = TextParser.new('My Text', XMLParser.new)
parser_xml.parse => "<text>My Text</text>"
puts "\n -- \n"
parser_json = TextParser.new('My Text', JSONParser.new)
parser_json.parse => "{ text: My Text }"
First of all, I created a BaseParser
class with an abstract method that must be implemented by the subclasses, the main intent of BaseParser
here, is to "simulate" an interface
, and guarantee that all subclasses will be required to implement these interface.
But, as Ruby is a dynamic language that uses Duck Typing as an approach to check if a given message must be executed or not for the receiver, we can get rid of these base class and use only the concrete parsers, the result must be the same.
class XMLParser
def parse(text)
"<text>#{text}</text>"
end
end
###
class JSONParser
def parse(text)
"{ text: #{text} }"
end
end
###
class TextParser
attr_reader :text, :parser
def initialize(text, parser)
@text = text
@parser = parser
end
def parse
puts(parser.parse(text))
end
end
parser_xml = TextParser.new('My Text', XMLParser.new)
parser_xml.parse => "<text>My Text</text>"
puts "\n -- \n"
parser_json = TextParser.new('My Text', JSONParser.new)
parser_json.parse => "{ text: My Text }"
With this approach is really simple to add new behaviors, for example, if I need to add a new parser, I just need to create the new parser code that implements the parse
method, and everything will continue to work:
class ReverseParser
def parse(text)
text.reverse
end
end
parser_xml = TextParser.new('My Text', ReverseParser.new)
parser_xml.parse => "txeT yM"
Besides, the Strategy Pattern will favor the use of Composition over Inheritance and the code will be in accordance with Open Closed Principle.
Well, that’s it, now you can create how many parsers do you need. This is how to use the Strategy pattern with Ruby.
Check out the full source code.
Top comments (2)
this code much better
Hi Aleksei, I had not thought of it that way but makes a lot of sense.
With this approach, you can avoid creating many instances of TextParser class.
Thanks a lot for your feedback!