Design Pattern: Strategy and Burger

Design Pattern: Strategy and Burger

Design patterns in life and Ruby - gain an intuitive understanding of OO design patterns by linking them with real-life examples.

 

Do you know how to order a burger? If so, I have a good news for you: you already know how to use the Strategy Pattern!

How so, you might ask? Well, let’s take a look at the definition of Strategy Pattern.

 

The Strategy Pattern:

- defines a family of algorithms,
- encapsulates each algorithm, and

- makes the algorithms interchangeable within that family.

 

Strategy lets the algorithm vary independently from clients that use it.

You might feel as confused as Nick Young

How does this have anything to do with burgers? Let’s think about burgers for a second.

There are a lot of varieties of burgers: veggie burger, cheeseburger, grilled chicken burger, double cheeseburger…

All of them have the same form: top bun + stuffing + bottom bun.

It’s the different stuffing in the middle makes burgers different from each other.

A cheeseburger has cheese and a beef patty in the middle as stuffing, where a grilled chicken burger has grilled chicken breast as stuffing.

 

Let’s revisit the definition in light of burgers:

The Strategy Pattern: defines a family of algorithms, encapsulates each algorithm, and makes the algorithms interchangeable within that family.

What is the family of algorithms in terms of burgers? It’s the family of different stuffings:

  • stuffing for a chicken burger = [grilled chicken breast]
  • stuffing for a cheeseburger = [cheese + beef patty]
  • stuffing for a double cheeseburger = [cheese + beef patty + cheese + beef patty]

They are encapsulated and are interchangeable with each other. Swap the stuffing of chicken burger with the stuffing of cheeseburger, you will get a cheeseburger!

 

Strategy lets the algorithm vary independently from clients that use it.

You can order whatever burger you like. But for a cook, making a burger follows the same general procedure: prepare the bun, cook the stuffing, and put the stuffing in between the top and bottom bun pieces.

A burger is a real-life example of using Strategy Pattern.

 

Let’s take a look at the code.

 

# Concrete Strategy
class GrilledChickenStuffing
  def cook
    stuffing = []
    stuffing << 'sliced tomato'
    stuffing << 'lettuce'
    stuffing << 'grilled chicken breast'
    stuffing
  end
end

class BeefPattyStuffing
  def cook
    stuffing = []
    stuffing << 'cheese'
    stuffing << 'grilled beef patty'
    stuffing
  end
end

# Context
class Burger
  attr_reader :top_bun, :bottom_bun
  attr_accessor :stuffing

  def initialize(stuffing)
    @top_bun = TOP_BUN
    @stuffing = stuffing
    @bottom_bun = BOTTOM_BUN
  end

  def cook
    burger = []
    burger << top_bun.cook
    burger << stuffing.cook
    burger << bottom_bun.cook
    burger
  end
end

chicken_burger = Burger.new(GrilledChickenStuffing.new)
cheese_burger = Burger.new(BeefPattyStuffing.new)

# change the chicken_burger to a cheese burger
chicken_burger.stuffing = BeefPattyStuffing.new
class GrilledChicken
  def cook
    stuffing = []
    stuffing << 'sliced tomato'
    stuffing << 'lettuce'
    stuffing << 'grilled chicken'
    stuffing
  end
end

class BeefPatty
  def cook
    stuffing = []
    stuffing << 'cheese'
    stuffing << 'grilled beef patty'
    stuffing
  end
end

 

There are three participants in the Strategy Pattern.

Strategy — declares an interface common to all supported algorithms. Context uses this interface to call the algorithm defined by a ConcreteStrategy.

ConcreteStrategy — implements the algorithm using the StrategyInterface.

Context — is configured with a ConcreteStrategy Object; maintains a reference to a Strategy object; may define an interface that lets Strategy access its data.

GrilledChickenStuffing and BeefPattyStuffing are our concrete strategies — each of them defines how its stuffing should be cooked. The Burger class is our context — it’s configured with a concrete strategy and it uses the concrete strategy later when a burger needs to be cooked.

In our burger example, we don’t have a specific class that declares the interface concrete strategies should implement. That’s because we don’t need to, thanks to Ruby’s duck typing: if it walks like a duck and talks like a duck, it is a duck. If it can be cooked as a burger stuffing, it is a burger stuffing.

Strategy declares the interface for which a concrete strategy should implement and for which a context can use. As you can see in the above code, both GrilledChickenStuffing and BeefPattyStuffing implement the cook method which is the method a context, meaning a user of a concrete strategy, expects the concrete strategy to provide.

 

The key idea of the Strategy Pattern.

Strategy Pattern is about pulling the varying algorithm out into a separate object. These objects become a family of algorithms the context can choose from. Each of these of objects, aka the strategies, does the same job and supports the same interface.

In our burger example, we have different stuffing strategies for a burger. And each of the concrete burger stuffing strategies supports the same interface by implementing the cook method.

It’s all about using composition (the user of the strategy has a strategy) and delegation (the user of the strategy delegate the job out). In our example, a burger has a stuffing, and it delegates the job of cooking the stuffing out.

 

Advantages of the Strategy Pattern

  • It achieves better separation of concerns by pulling out a set of strategies from a class and relives the Burger class of any responsibility for or knowledge of the stuffing.
  • It makes it easy to switch strategies at runtime because the pattern is based on composition and delegation rather than on inheritance.

 

Things to watch out for while considering using the Strategy Pattern

  • Data passing between context and strategy: if the implementation of a concrete strategy requires data from the context, you can either pass the data as parameters to a concrete strategy or pass the context itself into a concrete strategy so the concrete strategy can have access to the data through the context. Whichever way you choose, watch out for having the context and the concrete strategy tangled up too much.
  • Double check if you actually need the Strategy Pattern, the Template Method, or the Decorator Pattern.

 

Design Principles used in the Strategy Pattern

  • Encapsulate what varies
  • Favor composition over inheritance
  • Program to interfaces, not implementation

 

Now you have learned about the Strategy Pattern.

Here comes an important question: what’s your favorite burger strategy? ?


Thanks for reading. =)

Don't forget to subscribe!
Next time we will look at Template Method and …

Enjoyed the article?

My best content on Software Design, Rails, and Career in Dev. Delivered weekly.

Unsubscribe at anytime. I'll never spam you. Powered by ConvertKit

6 Comments Design Pattern: Strategy and Burger

  1. Dan

    These are great! A really effective way of translating the bewildering pattern descriptions into simple visuals.

Leave A Comment

Your email address will not be published. Required fields are marked *