This content originally appeared on Level Up Coding - Medium and was authored by Omkar Nath Mandal
I am not sure currently what else I will need but keep it open for modifications.
This is the third blog in the Software Design Pattern series. Let's continue to deep dive into another beautiful pattern that stresses a lot on OOP principles which we have already discussed in the previous blogs. Again the aim of the blog will be to answer the following ‘Doublouuuu’s’:
What problem does it solve?
What is the pattern exactly?
What are some real-life examples?
What’s the fancy definition to impress someone as a coder?
Let’s talk about the problem
I have heard a saying ‘People are born with natural talent’. Some people are just great by default at what they do. But what happens if someone is not born with a whole lot of natural talent (people like me 😩😩), the one who usually take more time to understand anything, the one who are slow to respond.
What happens to such people? Do they succeed? Do they fail ?
Well I would say it depends. At the end of the day if you are happy being a mediocre that is fine. You need not to prove anything to anyone. But if you want to be extraordinary then it will definitely take efforts.
It doesn’t matter if you are not born with natural talent and skills. You can always work hard and add new skills dynamically throughout your entire life.
Enough of enlightenment? Again are we really discussing design patterns here?😒😒
We are indeed discussing design patterns and patterns are not limited to just code. There are examples present around us and we just need to observe and relate.
Okay I hear you! for the sake of simplicity let’s just divert into some light example to understand the problem.
You might be wondering why there is a pizza photo on the top 😁. It's because we are going to talk a lot about pizza.
Let’s assume we are developing an app that tells the price of pizza after adding various combinations of flavors. We will start with very simple code. We will not start optimizing yet.
That’s one of the worst things to do when you design any system. What if people don’t like our pizza 😭😭😭?
What’s the point of optimization when it doesn’t even work?
So, let’s just KISS 😍I am talking about KEEP IT SIMPLE STUPID 😂😂😂 Gotcha!
Since we have already learned the power of composition over inheritance in our very first blog, we will utilize those learning and design a simple class diagram.
Let’s see what we have done so far
- We have created a Pizza interface that has a method definition of baking the pizza, a price function for each pizza, and of course name of the pizza. Simple right?
- We started serving 4 different types of pizza. So we just created our Pizza interface and implement the cost function according to the types of toppings and ingredients.
Well, I think this design serves our purpose for now. There is no need for over-complication and guess what ??
People liked our pizzas. People are ordering like crazy 🤩🤩
One day we got interesting feedback from one of our customers saying they ordered our Only Cheesy and Onion Pizza and experimented eating both slices together and they loved it. They asked if it would be possible for us to bake a new Pizza something like ‘Cheesy Onion’.
We unlike any other customer-centric pizza-making company take our customer feedback very seriously. Now we will think of extending our existing design. Let us add some more classes but this time we will think of other possibilities as well. Like how about making ‘Chicken Tikka with Corn’ or ‘Cheesy Corn’ or ‘Cheesy Chicken’.
Let’s look at the class diagram with these possibilities in mind.
Woah!! That’s a lot of classes and still, there can be a lot many combinations. This design definitely has some flaws.
Adding new classes is making our code base very difficult to maintain. A slight change in cost of Cheese or Corn or Onion will result in changes in God knows how many classes 😭😭
One solution which we can do is by creating a base abstract class PizzaBase and in this class, we can maintain the boolean variables for ingredients for cost calculation. Let’s look at the code for understanding this approach
Now we have separate boolean variables for each ingredient and there is no need for so many extra classes. We just have one concrete implementation as CheesyPizza. Before calculating the price of a pizza, we can just set the ingredient and get the updated price. At first, this approach seems okayish but what happens when we have to introduce more ingredients and also sizes. In our current implementation, we have completely ignored the size of the pizza. It simply means a lot of if-else in code.
I am not a big fan of IF-ELSE 😩
Let's see what are some problems with this approach. We talked something about Open Closed Principle, right? What happens if the price of some ingredient is dynamic which is usually the case. We need to change that in Base Pizza class and also we can anticipate some changes in other child classes as well. That’s definitely not cool.
Let us now think of some way so that adding any new ingredient doesn’t force us to change the code in so many places.
Now we will change our approach and think about layering. In the case of pizza, we are creating a layer of toppings and ingredients and the end product still remains the same which is Pizza. No matter how many toppings we add it will still be a pizza. We will think of decorating the pizza with different toppings and ingredients.
What if we have some contract which is of the same type as our end product (Pizza) but its responsibility is to decorate the pizza. Something like a ‘PizzaWithIngredient’. I am very particular about the naming here because if I name this contract as PizzaDecorator then this is contrasting with what I just said ‘ A pizza decorator is-not-a Pizza. It is not as same as the end product ’ right? Hence, we will name this PizzaWithIngredient.
Now let us look at the class diagram and the code to understand the scenario.
So we created a new ‘PizzaWithIngredient’ class which has a reference to Pizza and will decorate it with some ingredients. All the ingredient classes can extend this PizzaWithIngredient class and the cost will be calculated accordingly. Let us code this as well for complete understanding.
Now we can see there are no if-else blocks. We are just recursively decorating the pizza with different ingredients.
So, what’s the problem which we solved, and what was the scenario exactly?
The scenario was we have to dynamically add some behavior/decoration to some objects instead of having it statically defined at compile time.
Enough of Pizza! Show me some more real-life examples where we can use this pattern!
Now we will look at how we can utilize this pattern to change the behavior of objects without changing their code. We will take one simple example and then we will look at a very cool example straight out of Head First book which shows how this pattern is used in Java for input streams.
Example 1: Let’s say we are building a file reader and compressor. In simple words, our system will read the file, do the encoding, and then compress the file. Again the encryption and compression algorithms will not be implemented exactly as a working code because the goal is not the implementation here. Let us look at the code to understand this🥷
Let us understand what we have done:
- We defined one interface as DataSource which has two methods ‘write data’ and ‘read data’
- There is a concrete implementation as ‘FileDataSource’ which deals with data whose engress point is a file.
- Then we have ‘DataSourceWithEnhancement’ class which is also of the same type as DataSource. Look at pizza example above why this is necessary.
- Then we have two different decorators. One which encrypts the data and other which compresses the data.
Example 2: As promised its time for a cool example from Java. A lot of Java developers will relate to this🤩 We are going to look at the very famous InputStream. InputStream is an abstract class and we have different decorators like BufferedInputStream,GzipInputStream , FileInputStream etc. which takes an instance of the abstract class and decorate it.
Lets look at the code which is given in book🥷
Let’s understand what we are doing here:
- FileInputStream is the Decorator class for InputStream
- LowerCaseInputStream is the concrete decorator for reading the content in lowercase characters.
- In LowerCaseInputStream constructor we are passing the reference to the input stream.
So what’s the official definition for this pattern?
According to Head First book, ‘ The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality ’.
That’s it. We have successfully covered Decorator Pattern. Again please go through the references for a better understanding and more examples and please don’t hate me if you find anything wrong or incomplete🥺 . I am trying to do better ❤️
References:
- https://springframework.guru/gang-of-four-design-patterns/decorator-pattern/
- https://www.baeldung.com/java-decorator-pattern
- https://refactoring.guru/design-patterns/decorator/java/example
- https://howtodoinjava.com/design-patterns/structural/decorator-design-pattern/
- https://www.journaldev.com/1540/decorator-design-pattern-in-java-example
Let’s add some spice to our lonely lives was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Omkar Nath Mandal
Omkar Nath Mandal | Sciencx (2022-04-14T00:35:17+00:00) Let’s add some spice to our lonely lives. Retrieved from https://www.scien.cx/2022/04/14/lets-add-some-spice-to-our-lonely-lives/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.