DEV Community

Luiz Cezer
Luiz Cezer

Posted on

Design Patterns in Ruby: Strategy Pattern

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.

References

Top comments (2)

Collapse
 
mirzalazuardi profile image
Mirzalazuardi Hermawan

this code much better

Collapse
 
lcezermf profile image
Luiz Cezer

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!