Strategy Design Pattern

Brief Overview of The Strategy Pattern

The Strategy or Policy Pattern is one of the behavioral design patterns from the gang of four design patterns book. It is primarily used when the specific implementation or algorithm cannot be determined until run time.

When To Implement

There are several common software design problems that are easily solved by implementing the strategy pattern:

  • There are a number of classes that have the same attributes but vary in a specific method implementation. By implementing the strategy pattern a single method call can now have many behaviors.
  • There is one action that can be implemented in various ways.  The strategy pattern will allow for the correct implementation to be chosen at runtime.
  • There is a class that has many different behaviors that are being determined by multiple if statements. The strategy pattern helps here by moving the related conditional branches into their own classes.

Strategy Pattern Components

The strategy pattern is typically comprised of three components and a client. The first component is the context which is used to return the correct concrete strategy at run time.  The second component is a class that acts as the base strategy which is typically a parent class that has the strategy method implemented as an abstract method (the whole class is abstract). The third component is the concrete strategies which inherit from the parent strategy class and have specific implementations of the abstract method.  The client will have a context and the context will have a reference to the strategy object and using polymorphism will be configured with the correct concrete strategy at run time.

Implementation of the Strategy Pattern

This implementation uses the basic structure of the design pattern.

The Context

The context is essentially used to determine which concrete strategy is needed by passing in some condition, for this example the temperature to convert from, and then instantiating it and allowing the client to interact with it using the sub class’s concrete implementation of the abstract method.


class TempConvertInterfaceContext

   #the initialize methods figures out the concrete strategy and then assigns it
   #to the convert strategy instance variable.  Typically this would be an if
   #statement in some implementations but ruby allows us to do a little meta
   #programming.

   def initialize(convert_from)
     @convert_strategy =
     Object.const_get("#{convert_from.capitalize}Strategy").new
   end

   def convert_temperature(convert_to, temperature)
     @convert_strategy.convert_temperature(convert_to, temperature)
   end

end

Abstract Interface For Concrete Strategies

All concrete instances of the strategy should inherit from this class and implement the abstract method so the context and client do not have to care about the implementation. The point of the interface is to define a method that must be implemented in all sub classes.


class StrategyInterface

  #In ruby we really don't need this interface at all.  Ruby will throw
  #a NoMethod error if a method doesn't exist which is essentially what
  #I am doing here. For sake of sticking to the GOF implementation I will
  #use this class and method as an interface.
  def convert_temperature(convert_to, temperature)
    raise "Must implement this method."
  end

end

Concrete Implementations of Strategy Interface

Each of these classes inherits from the StrategyInterface class and implements the abstract convert_temperature method.

class KelvinStrategy < StrategyInterface

  def convert_temperature(convert_to, temperature)
    case convert_to
    when 'celsius'
      temperature - 273.15
    when 'fahrenheit'
      temperature * 9.0 / 5.0 - 459.67
    when 'kelvin'
      temperature
    else
      raise "This Scale is currently not supported"
    end
  end

end

class FahrenheitStrategy < StrategyInterface

  def convert_temperature(convert_to, temperature)
    base_calculation = (temperature - 32) * 5.0 / 9.0
    case convert_to
    when 'celsius'
      base_calculation
    when 'kelvin'
       base_calculation + 273.15
    when 'fahrenheit'
       temperature
    else
       raise "This Scale is currently not supported"
    end
  end
end

class CelsiusStrategy < StrategyInterface

  def convert_temperature(convert_to, temperature)
    case convert_to
    when 'fahrenheit'
      temperature * 9.0 /5.0 + 32
    when 'kelvin'
      temperature + 273.15
    when 'celsius'
      temperature
    else
      raise "This Scale is currently not supported"
    end
  end

end

The Client

The client is essentially the driving mechanism that needs to use the strategy but doesn’t know which of the strategies it will need until runtime.


class Client
  # The client has a context which is used to determine the
  # specific concrete strategy and then return that strategy back to the client.
  # The values in this array are different concrete strategies that
  # will be chosen at runtime to implement.
  ['fahrenheit_to_celsius', 'fahrenheit_to_kelvin', 'celsius_to_fahrenheit',
  'celsius_to_kelvin', 'kelvin_to_celsius', 'kelvin_to_fahrenheit'].each do | temperature_scale_conversion_direction |
      #Parsing the string into the two temperature scales.
      convert_from, convert_to = temperature_scale_conversion_direction.split(/_to_/)
      #This instantiates the context with the
      #specific concrete strategy and returns the concrete strategy.
      temp_converter = TempConvertInterfaceContext.new(convert_from)
      #Printing the converted temperature.
      puts temp_converter.convert_temperature(convert_to, 0)
    end
end

Get the Code

Here is the Github repository for the code. You will notice I also made another version more idiomatic to ruby using Procs.

Final Thoughts

The Strategy Pattern is extremely handy to use when you need to check the type of something and then perform an action on it. If the type is not known until runtime, using the context to return the correct strategy removes the burden from the client, who in all honesty should not care about the implementation at all.

Ruby Gem

I created a ruby gem from this post it can be found here.  The source code for the gem can be located here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s