Design patterns in life and Ruby - gain an intuitive understanding of OO design patterns by linking them with real-life examples.
Factory Patterns are about encapsulating object creation.
But before diving into details of these patterns, let’s talk about cheesecakes. Because cheesecakes are about … happiness!
Let’s focus our gaze at six of my personal favorites: Original Cheesecake, Ore0 Cheesecake, Coffee Cheesecake, Tiramisu Cheesecake, S’mores Cheesecake, and Hazelnut Cheesecake.
Here is how we make a cheesecake:
def make_cheesecake(type) cheesecake = nil case type when 'oreo' cheesecake = OreoCheesecake.new when 'coffee' cheesecake = CoffeeCheesecake.new when 'tiramisu' cheesecake = TiramisuCheesecake.new when 'smores' cheesecake = SmoresCheesecake.new when 'hazelnut' cheesecake = HazelnutCheesecake.new else cheesecake = OriginalCheesecake.new end cheesecake.make_crust cheesecake.add_layers cheesecake.bake cheesecake.refrigerate cheesecake.add_toppings cheesecake end
Create a cheesecake instance based on the selected type -> Make crust -> Add layers on top of the crust -> Bake it -> Refrigerate it -> Add toppings to the cake -> Return the cake!
Wait … that Mango key lime cheesecake looks very tempting...
Let me add it to my list:
def make_cheesecake(type) cheesecake = nil case type when 'oreo' cheesecake = OreoCheesecake.new when 'coffee' cheesecake = CoffeeCheesecake.new when 'tiramisu' cheesecake = TiramisuCheesecake.new when 'smores' cheesecake = SmoresCheesecake.new when 'hazelnut' cheesecake = HazelnutCheesecake.new when 'mango' cheesecake = MangoKeyLimeCheesecake.new else cheesecake = OriginalCheesecake.new end cheesecake.make_crust cheesecake.add_layers cheesecake.bake cheesecake.refrigerate cheesecake.add_toppings cheesecake end
One second …
I have been having too much caffeine lately. I don’t want the coffee cheesecake to be on my list anymore. Let me update the make_cheesecakemethod
again.
def make_cheesecake(type) cheesecake = nil case type when 'oreo' cheesecake = OreoCheesecake.new when 'tiramisu' cheesecake = TiramisuCheesecake.new when 'smores' cheesecake = SmoresCheesecake.new when 'hazelnut' cheesecake = HazelnutCheesecake.new when 'mango' cheesecake = MangoKeyLimeCheesecake.new else cheesecake = OriginalCheesecake.new end cheesecake.make_crust cheesecake.add_layers cheesecake.bake cheesecake.refrigerate cheesecake.add_toppings cheesecake end
Oooh…. they have a low carb version of cheesecake. It’s always nice to have a low carb option on the list. It needs to be on my list!
def make_cheesecake(type) cheesecake = nil case type when 'oreo' cheesecake = OreoCheesecake.new when 'tiramisu' cheesecake = TiramisuCheesecake.new when 'smores' cheesecake = SmoresCheesecake.new when 'hazelnut' cheesecake = HazelnutCheesecake.new when 'mango' cheesecake = MangoKeyLimeCheesecake.new when 'low_carb' cheesecake = LowCarbCheesecake.new else cheesecake = OriginalCheesecake.new end cheesecake.make_crust cheesecake.add_layers cheesecake.bake cheesecake.refrigerate cheesecake.add_toppings cheesecake end
Since the first time we defined make_cheesecake
, we have updated it three times. Each change was for the exact same reason — to update my cheesecake list. And everything else, make_crust
, add_layers
, bake
, refrigerate
, and add_toppings
, remained the same.
Sorry for keep changing my mind every three seconds.
But as they say:
change is the only constant in life (and software development).
To be honest, we will need to change the list at least one more time: pumpkin cheesecake will be available from September. It’s WORLD FAMOUS! Without a doubt, we need to add it to the list once September arrives.
Oops, that means we need to remove it from the list when the holiday season passes.
It’s obvious that my cheesecake list changes often.
In this case, there is a design principle we should try to follow:
encapsulate what varies.
It’s time for a Cheesecake Factory!
class CheesecakeFactory def create_cheesecake(type) case type when 'oreo' OreoCheesecake.new when 'tiramisu' TiramisuCheesecake.new when 'smores' SmoresCheesecake.new when 'hazelnut' HazelnutCheesecake.new when 'mango' MangoKeyLimeCheesecake.new else OriginalCheesecake.new end end end
The CheesecakeFactory
is a simple class. All it does is creating and returning the correct cheesecake based on a given type.
With the help of CheesecakeFactory
, make_cheesecake
method becomes much simpler.
def make_cheesecake(type) factory = CheesecakeFactory.new cheesecake = factory.create_cheesecake(type) cheesecake.make_crust cheesecake.add_layers cheesecake.bake cheesecake.refrigerate cheesecake.add_toppings cheesecake end
The make_cheesecake
method now can focus on the actual steps that go into making a cheesecake without having to worry about different cheesecake types.
Our CheesecakeFactory
is an example of using the Simple Factory. Simple Factory is used for encapsulating object creation.
The Factory Pattern Family
Besides Simple Factory, there are two other members of the Factory Pattern family: Factory Method and Abstract Factory. We won't go into details of these two patterns.
In a nutshell, Factory Method and Abstract Factory use inheritance. Factory Method is about creating one type of object and Abstract Factory is about creating a family of different types of objects. All three of them are about encapsulating object creation by using the design principle: encapsulate what varies.
Benefit of using Simple Factory
Pulling the logic of creating the correct cheesecake based on a given type is a small move that gives us lots of benefits. The biggest benefit being we can modify the cheesecake list without touching the make_cheesecake
method and its test. All we need to do is update the CheesecakeFactory
class and leave make_cheesecake
and its test alone.
We want to separate the parts that vary often from the stable parts. Because each time we modify a part of our code, we might introduce bugs. The parts that vary are the fragile parts of our system. We want to keep the stable parts away from the fragile parts, so if we did introduce bugs when updating a part of the system, it would be easier for us to locate the bug.
Takeaways:
- Factory Patterns are used for encapsulating object creation.
- Design Principle: encapsulate what varies.
I need to run to get a cheesecake now.
Next time, we will take a look at some waaaaaaaaffles!
Don't forget to subscribe so you won't miss the next post ?.
Enjoyed the article?
My best content on Software Design, Rails, and Career in Dev. Delivered weekly.
Nice article. Clear and to the point.
I loved the YouTube video you made for this.