Decorator pattern for object composition
Nice to meet you again! Hope you didn’t miss my 2 previous posts about Strategy & Observer patterns👻:
- Strategy: https://medium.com/towardsdev/strategy-pattern-for-independent-algorithms-kotlin-70ed24c7bd8b
- Observer: https://towardsdev.com/observer-pattern-for-loose-coupling-kotlin-f5ab804609bb
Be sure to read them to get the better understanding of how this series unleashes🤟 As before, for super-duper detailed explanation buy the book: https://www.oreilly.com/library/view/head-first-design/9781492077992/
Structure:
- Intro
- Problem
- Design principles to follow
- Final solution code (with additional theory on this pattern as it’s pretty blurry)
- Drawing
Let’s dive asap to the gist!
Decorator pattern
Intro
When you develop something in a haste, it’s no wonder that such a work will bring lots of underlying issues👎🏻 The most insidious of them is bad design which makes it pretty arduous to resolve all the mess around (you’ll see an example in the Problem section).
Solution? Ah…😪 There is no spell that magically untangles mess in a moment. However🖐 Being cognizant of design patterns and OOP principles will save you time and future hustle as you’ll roughly compare your business task implementation with those 2 mentioned things and decide: how to make my software not simply working, but extensible and manageable in the future🛠 Yes, it sounds vague, but with experience you’ll spot such points faster.
Although, it doesn’t mean that you shouldn’t create temporary version to get the idea of how future-to-be-product should look like
Problem
So, you have a base class that is extended by many other classes. For simplicity, let’s imagine:
- base class is
Beverage
- children are types of that
Beverage
So, at some moment you may think it’s even okay. But what if we have a cafe with tens, hundreds of Beverage
s? And the main difference is ingredients❓
- So we need to add new similar classes
- What if implementation of
cost()
will change or new method appear?
Recall when I said about bad design in the Intro section
Then you may think: put ingredients
variables inside parent class and scatter getters
& setters
(Java version || some methods in Kotlin/other language). At first, let’s observe this “modified” solution:
Problems here? Let’s enumerate them:
- What if
price
for ingredient will change? We’ll need to change existing code => bad! - New ingredients will force us again to alter code
- All children of
Beverage
will inherit those checks for ingredients, but what if we don’t need them?
etc…❗️
Design principles
We all have heard about SOLID (I hope😰). What does “O” mean?
- Open-Closed principle: our objects/classes should be open for extension, but closed for modification.
Yeah yeah, it sounds like vice versa stuff. I thought that way when firstly heard about it, but let me tell you the real meaning:
Our class mustn’t allow others to be modified: for example some methods or properties should stay as the same, however, our class:
- can be extended without making change to the initial code. How? Recall Observer pattern (link at the beginning) where we extend Subject with concrete observer. We leveraged methods from Subject and further can change results from that class without making changes to the Subject class itself
- We can specifically make some methods open, others private/protected
You may question yourself: how can I engineer systems which will follow this rule🧐? Answer:
- with experience
- you shouldn't do it as new levels of abstraction adds complexity => concentrate on the chunks which are likely to be under alteration and apply the principle there
- use already created patterns to ease up your development
Final solution code
Following the link below, you can look at my code which I’ll explain further and match with pattern theory
At first, let me introduce another usage of inheritance🙌: inheritance for type matching rather than simple behavior inheriting. How does it work?
- Child IS-A Parent, but doesn't bluntly use methods. Actually, it’s for type matching, i.e. making this class of parent. WHY USE SUCH A GIMMICK??
Let me draw a diagram of the Decorator pattern:
Main Component
/ \
Concrete Component Main Decorator
/ \
Concrete Decorator 1 Concrete Decorator 2
Here Main Decorator
needs to be of the same type as Concrete Component as former will decorate latter. Hence, we use inheritance for type matching.
How does it look in a more picture-like version?
DarkCoffee
decorated by Mocha
decorated by Whip
etc
It’s like a snowball. In the center we have our Concrete Component
(child of Main class, i.e. Beverage)
, and then there are Decorators added like new layers.
Info about those decorators:
- They are of same super-type as object they decorate
- There can be multiple decorators applied
- We can pass already decorated object (and actually we do it)
- We can decorate objects at runtime
- Decorators can add it’s own behavior before/after decorating the object
In my example we won’t go too deep into new methods and stuff like that to lessen the mental burden.
We acquire new behavior not by inheriting from super-type/class, but by composing objects together
Let’s analyze the code:
mainComponent.kt
is ourabstract class
which gives birth to everythingconcreteComponent.kt
is a child which will be decoratedmainDecorator.kt
isabstract class
for decorators. It inherits for type which was discussed aboveconcreteDecorator.kt
&concretteDecorator2.kt
are examples of decorators which will be applied onconcreteComponent
How does everything work?
- look at
main.kt
: we initialize new component and wrap it into 2 decorators. When we callolivesPizza.cost()
the chain is triggered:
- we enter
concreteDecorator.kt cost()
which in turn calls it’scurrentPizza
. - This
currentPizza
isconcreteDecorator2.kt
: Cheese. It calls it’scurrentPizza
which isFreshPizza
aka concrete component. - This concrete component returns 25. Then this 25 + 5 returns from
Cheese
akaconcreteDecorator2.kt
toconcreteDecorator.kt
where final 4 is added🙌
Same applies to currentDescription()
method. Can you untangle it? If something is obscure, let me know in the comments!
Drawing✍🏻
Here you can observe the explained above schema in flesh. On the left side there is a general blueprint, whilst on the right side there is an example of code in my GitHub repo.
Drop message in comment if you wanted me to elucidate it
IS-A means inheritance. But don’t forget about inheritance for type matching, not simple behavior inheritance☝🏼
Outro😪
Academic definition of Decorator pattern: adds extra responsibilities to an object dynamically. It provides ability to extend functionality without subclassing over-usage
I know that this pattern is a little bit more nebulous compared to the previous, but hope you’ve got the crux👋
You can find me:
- LinkedIn: www.linkedin.com/in/sleeplesschallenger
- GitHub: https://github.com/SleeplessChallenger
- Leetcode: https://leetcode.com/SleeplessChallenger/
- Telegram: @SleeplessChallenger