Design Pattern: Decorator and Waffle

Design Pattern: Decorator and Waffle

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

 

The decorator pattern is about adding additional features to an existing object easily.

Does that sound like French?

No worries. We will come back to this later.

Let’s take a look at some waffles first!

 

The genius part about waffles is that they start plain and simple. Because they are plain, almost everything tastes good with them. The most common toppings for waffles are strawberries, blueberries, blackberries, bananas, almonds, whip cream, Nutella, and syrups.

If we try to create a collection of different waffle objects, there will be StrawberryWaffle, BlueberryWaffle, BlackberryWaffle, BananaWaffle, AlmondWaffle, WhipCreamWaffle, NutellaWaffle, and SyrupWaffle.

Wait, we can have both strawberries and blueberries on the same waffle, which gives us a StrawberryBlueberryWaffle. We can also have both strawberries and blackberries on the same waffle, which gives us a StrawberryBlackberryWaffle. No one is forbidding us from putting three toppings on the same waffle, which gives us a StrawberryBlueberryBlackberryWaffle.

To make things simple, if we only consider strawberries, blueberries, and blackberries as potential toppings, there are eight different combinations1.

Does this mean we need to create eight different objects for our waffle collection?

If we add bananas into our potential toppings list, there are 16 different combinations2.

It’s obvious that adding a single topping to our toppings list causes an explosion in our waffle collection. It’s not feasible to create a different waffle class for each possible combinations of toppings. There must be a better way to do this.

 

What if, when we want a StrawberryWaffle, instead of creating a StrawberryWaffle directly, we create a Waffle and add strawberries to it?

What about StrawberryBlueberryWaffle then? ???

???We can create a Waffle, add strawberries to it, and add blueberries to it!???

That solves the problem!

 

Creating Waffle Classes

Let’s take a look at the plain waffle class:

class Waffle
  attr_reader   :waffle
  
  def initialize
    @waffle = 'This is a waffle'
  end
  
  def serve
    puts waffle
  end
  
  def eat
    puts 'Eat a bit of the waffle'
  end
end

You can create a waffle, serve it, and eat it like this:

 

And here is the StrawberryWaffle class:

class StrawberryWaffle
  attr_reader   :waffle
  attr_accessor :topping
  
  def initialize(waffle)
    @waffle = waffle
    @topping = 'strawberries'
  end
  
  def serve
    waffle.serve
    puts "  topped with #{topping}"
  end
  
  def eat
    waffle.eat
    puts "  and then eat some #{topping}"
  end
end

Notice we pass a waffle object inside the StrawberryWaffle constructor in order to create a StrawberryWaffle. 

 

The StrawberryWaffle class has:

  1. The passed-in waffle
  2. Strawberries as a topping
  3. A serve method that calls the passed-in waffle’s serve method and then prints  topped with strawberries
  4. An eat method that calls the passed-in waffle’s eat method and then prints  and then eat some strawberries

 

You can create a strawberry waffle, serve it, and eat it like this:

 

Similarly, here are the BlueberryWaffle and BlackberryWaffle classes:

class BlueberryWaffle
  attr_reader   :waffle
  attr_accessor :topping
  
  def initialize(waffle)
    @waffle = waffle
    @topping = 'blueberries'
  end
  
  def serve
    waffle.serve
    puts "  topped with #{topping}"
  end
  
  def eat
    waffle.eat
    puts "  and then eat some #{topping}"
  end
end

class BlackberryWaffle
  attr_reader   :waffle
  attr_accessor :topping
  
  def initialize(waffle)
    @waffle = waffle
    @topping = 'blackberries'
  end
  
  def serve
    waffle.serve
    puts "  topped with #{topping}"
  end
  
  def eat
    waffle.eat
    puts "  and then eat some #{topping}"
  end
end

And you can use them like this:

 

Pulling the Common Part Out

Noticing the StrawberryWaffle class, the BlueberryWaffle class, and the BlackberryWaffle class are almost identical except for their topping, we can pull the common parts out as a parent class.

class WaffleDecorator
  attr_reader   :waffle
  
  def initialize(waffle)
    @waffle = waffle
  end
  
  def serve
    waffle.serve
    puts "  topped with #{topping}"
  end
  
  def eat
    waffle.eat
    puts "  and then eat some #{topping}"
  end
  
  def topping
    raise NotImplementedError
  end
end

In WaffleDecorator,topping is no longer an attribute of the object. Instead, it’s a method that can be overridden by a child class.

 

Now we can rewrite StrawberryWaffle, BlueberryWaffle, and BlackberryWaffleto inherit WaffleDecorator to gain these common functionalities:

class StrawberryWaffle < WaffleDecorator
  def topping
    'strawberries'
  end
end

class BlueberryWaffle < WaffleDecorator
  def topping
    'blueberries'
  end
end

class BlackberryWaffle < WaffleDecorator
  def topping
    'blackberries'
  end
end

And they should still work the same as before:

 

Here are the classes we create:

 

Creating a BlueberryStrawberry Waffle

Now we have Waffle, StrawberryWaffle, BlueberryWaffle,and BlackberryWaffle. It’s time to accomplish the goal we originally set out: create a Waffle, add strawberries to it, and add blueberries to it.

Just like this:

And we can:

 

What is happening?! ???

Let’s take a closer look at how we created blueberry_strawberry_waffle.

First, we created a plain_waffle with Waffle:

plain_waffle = Waffle.new

Then we created strawberry_waffle by passing theplain_waffle into the StrawberryWaffle constructor:

strawberry_waffle = StrawberryWaffle.new(plain_waffle)

It’s worth noting that when we create the strawberry_waffle, we hold the passed-in plain_waffle as an instance variable of strawberry_waffle:

As we can see,strawberry_waffle.waffle and plain_waffle are the same object:

 

At this point, when we call strawberry_waffle.serve we first call plain_waffle.servethen printtopped with strawberries.

 

Similarly, for strawberry_waffle.eat, we first call plain_waffle.eat then print and then eat some strawberries.

 

Lastly, we create blueberry_strawberry_waffle by passing thestrawberry_waffle into the BlueberryWaffle constructor:

blueberry_strawberry_waffle = BlueberryStrawberryWaffle.new(strawberry_waffle)

When we create the blueberry_strawberry_waffle, we hold the passed-in strawberry_waffle as an instance variable of blueberry_strawberry_waffle:

 

When we call blueberry_strawberry_waffle.serve we first call strawberry_waffle.serve, which calls plain_waffle.serve then prints topped with strawberries, and then print topped with blueberries.

 

Similarly, when we call blueberry_strawberry_waffle.eat we first call strawberry_waffle.eat, and which calls plain_waffle.eat then print and then eat some strawberries, and then print and then eat some blueberries

 

The Key of the Magic

strawberry_waffle is built on top of plain_waffle,and blueberry_strawberry_waffle is built on top of strawberry_waffle.

The key of being able to build waffles on top of each other is all waffles have to obey the same interface. All waffles have a serve method and an eat method.

That’s why within the StrawberryWaffle/BlueberryWaffle/BlackberryWaffle classes, we are confident that the passed-in waffle has a serve method and an eat method. And we can leverage the serve method and the eat method from the passed-in waffle when defining a new serve method and a new eat method.

Essentially, a WaffleDecorator doesn’t care about the kind of waffle. It can be a plain_waffle,a strawberry_waffle, or an alien-waffle.

All that matters is that a WaffleDecorator takes a waffle and returns an enhanced waffle. The waffle it takes and the waffle it returns obey the same interface. Since all decorators taking and returning waffles obeying the same interface, the result of a decorator can be passed into another decorator. 

Just like this:

or this:

 

Now with Waffle, StrawberryWaffle, BlueberryWaffle,and BlackberryWaffle, we can create all eight different waffles.

Adding banana into our topping list is as easy as:

class BananaWaffle < WaffleDecorator
  def topping
    'banana'
  end
end

 

You just Learned the Decorator Pattern! ?

 

Here’s its definition:

Decorator attaches additional responsibilities to an object dynamically.

 

Takeaways:

  1. The decorator pattern is about adding additional features to an existing object easily.
  2. The object to be decorated (the one being passed into decorators) and objects returned from decorators have to obey the same interface.

 

Thanks for reading! I hope you enjoy the article.

Don't forget to subscribe! Next time we will take a look at some …

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

[1] PlainWaffle, StrawberryWaffle, BlueberryWaffle, BlackberryWaffle, StrawberryBlueberryWaffle, StrawberryBlackberryWaffle, BlueberryBlackberryWaffle, and StrawberryBlueberryBlackberryWaffle.

[2] C(4, 0) + C(4, 1) + C(4, 2) + C(4, 3) + C(4, 4) = 16

1 Comment Design Pattern: Decorator and Waffle

Leave A Comment

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