DEV Community

Luiz Cezer
Luiz Cezer

Posted on • Updated on

Design Patterns in Ruby: Template Method pattern

By definition, the pattern defines a skeleton of an algorithm, but leave certain parts of this algorithm to be implemented by subclasses. Those subclasses can override the main behavior, without a need to change the main structure of the base algorithm.

In other words, the base class defines a skeleton and subclasses must fill this skeleton with your own implementation.

Deal with different membership plans

Let's say that you need to add a membership feature to your application, and the membership has two different plans, the basic and the premium. Each of these plans has their own title, description and a bunch of benefits.

At first try, you can simply add an if statement to check which type plan will be created, and it seems a good idea.

class Plan
  attr_reader :title, :description, :benefits

  def initialize(kind)
    if kind == :basic
      @title = "I'm the basic plan"
      @description = "My description of basic plan"
      @benefits = ['It is free', 'Access to one account', 'Basic features']
    else
      @title = "I'm the other plan"
      @description = "My description of other plan"
      @benefits = ['No benefits']
    end
  end

  def output
    output = <<-STRING
      Title: #{title}
      Description: #{description}
      Plan benefits: #{benefits.join(', ')}.
    STRING

    puts(output)
  end
end

####

basic = Plan.new(:basic)
basic.output

"""
Title: I'm the basic plan
Description: My description of basic plan
Plan benefits: It is free, Access to one account, Basic features.
"""

other_plan = Plan.new(:other)
other_plan.output

"""
Title: I'm the other plan
Description: My description of other plan
Plan benefits: No benefits.
"""

But, what will happen if we need to add a check for a new plan called premium? If we keep in this approach, the code will look like this:

class Plan
  attr_reader :title, :description, :benefits

  def initialize(kind)
    if kind != :basic
      @title = "I'm the basic plan"
      @description = "My description of basic plan"
      @benefits = ['It is free', 'Access to one account', 'Basic features']
    elsif kind == :premium
      @title = "I'm the premium plan"
      @description = "My description of premium plan"
      @benefits = ['It is paid', 'Access to ten accounts', 'Premium features', 'Access to support']
    else
      @title = "I'm the other plan"
      @description = "My description of other plan"
      @benefits = ['No benefits']
    end
  end

  # ...
end

And what if we need to add a new plan called master premium? Yeah, this approach will not scale well and will deny the code to be flexible and with support many plans.

Using Template Method Pattern to allow multiple plans

As I said before, the main definition of Template Method pattern is to define a skeleton and let the subclasses to fill this skeleton with their own implementation.

So let's define the base class:

class Base
  def title
    raise 'Must implement'
  end

  def description
    raise 'Must implement'
  end

  def benefits
    raise 'Must implement'
  end

  def output
    output = <<-STRING
      Title: #{title}
      Description: #{description}
      Plan benefits: #{benefits}.
    STRING

    puts(output)
  end
end

It's pretty basic, the Base class just define 3 methods title, description and benefits. The output method, contains the skeleton algorithm with the behaviors that subclasses will implement.

The importance of Base class is to define an interface that all subclasses must follow and implement.

Now let's create the subclasses:

class BasicPlan < Base
  def title
    "I'm the basic plan"
  end

  def description
    "My description of basic plan"
  end

  def benefits
    ['It is free', 'Access to one account', 'Basic features'].join(', ')
  end
end

###

class PremiumPlan < Base
  def title
    "I'm the premium plan"
  end

  def description
    "My description of premium plan"
  end

  def benefits
    [
      'It will cost USD 10.00',
      'Access to ten accounts',
      'Premium features',
      'You will receive a gift on your birthday'
    ].join(', ')
  end
end

Each subclass implement your own behavior for each method without worrying about output method, your only
a concern is to implement the hook methods, that will be used inside Base#output.

And what happens if we need to add a new membership plan? Well, it very simple, we just need to create the new class and implement Base class methods.

class AwesomePlan < Base
  def title
    "I'm the awesome plan"
  end

  def description
    "My description of awesome plan"
  end

  def benefits
    [
      'It will cost USD 1,000.00',
      'Access to a hundread accounts',
      'Awesome features',
      'You will receive a gift on your birthday',
    ].join(', ')
  end
end

###

"""
Title: I'm the awesome plan
Description: My description of awesome plan
Plan benefits: It will cost USD 1,000.00, Access to a hundread accounts, Awesome features, You will receive a gift on your birthday.
"""

Well, this is it, this is how to use the Template Method pattern with Ruby.

References

Top comments (0)