Skip to content

Commit 1702a0d

Browse files
Decorator Pattern Docs/Notes Added
1 parent 14b4c3a commit 1702a0d

File tree

11 files changed

+756
-7
lines changed

11 files changed

+756
-7
lines changed

content/Decorator Pattern.md

Lines changed: 324 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,324 @@
1-
<marquee>Comming Soon</marquee>
1+
#structural
2+
## Definition
3+
4+
The Decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
5+
6+
---
7+
## Real World Analogy
8+
9+
Consider we are running a **coffee shop**. In the shop, we offer a **variety of coffee** along with options for customers to add **extras** like **extra milk, sugar, coffee powder**, and more.
10+
11+
If a customer buys only a **Plain Coffee**, they should be charged **only the price of the Plain Coffee**. However, if the customer wants **extra coffee powder** (or any other add-on) in their Plain Coffee or any other type of coffee, the **additional cost** of the add-on should be applied **on top of the base cost** of the selected coffee.
12+
13+
To design such a structure, we implement the **Decorator Pattern**. This approach eliminates the need for **if-else statements** in the classes. Instead, we **decorate** the object within the **same class**, maintaining flexibility and scalability. See the Example below:
14+
15+
![[Pasted image 20250224235107.png]]
16+
_Coffee Prices Chart_
17+
18+
The Image shows how the cost is incurred if user adds extras to his coffee. Below is the Implementation of such pattern.
19+
20+
### Design
21+
```mermaid
22+
---
23+
title: Decorator Pattern
24+
---
25+
classDiagram
26+
direction TB
27+
28+
%% Interface for Coffee
29+
class Coffee {
30+
<<interface>>
31+
+getDescription() String
32+
+getCost() int
33+
}
34+
35+
%% Concrete Coffee implementations
36+
class PlainCoffee {
37+
+getDescription() String
38+
+getCost() int
39+
}
40+
class CappuccinoCoffee {
41+
+getDescription() String
42+
+getCost() int
43+
}
44+
45+
%% Abstract Decorator that implements Coffee and holds a Coffee reference
46+
class CoffeeDecorator {
47+
<<abstract>>
48+
- _coffee : Coffee
49+
+CoffeeDecorator(coffee: Coffee)
50+
+getDescription() : String
51+
+getCost() : int
52+
}
53+
54+
%% Concrete Decorators
55+
class MilkDecorator {
56+
+MilkDecorator(decoratedCoffee: Coffee)
57+
+getDescription() String
58+
+getCost() int
59+
}
60+
class SugarDecorator {
61+
+SugarDecorator(decoratedCoffee: Coffee)
62+
+getDescription() String
63+
+getCost() int
64+
}
65+
66+
PlainCoffee --|> Coffee
67+
CappuccinoCoffee --|> Coffee
68+
CoffeeDecorator --|> Coffee
69+
MilkDecorator --|> CoffeeDecorator
70+
SugarDecorator --|> CoffeeDecorator
71+
CoffeeDecorator --> Coffee : _coffee
72+
73+
```
74+
_Design of the Coffee Shop_
75+
76+
---
77+
## Coding Decorator Pattern
78+
79+
```java title:Coffee.java
80+
// Coffee.java
81+
interface Coffee {
82+
public String getDescription();
83+
public int getCost();
84+
}
85+
```
86+
The `Coffee` interface acts as an **abstraction layer** between the **variety of coffee types**. By implementing this interface, you can create **as many coffee varieties** as needed.
87+
```java title:PlainCoffee.java
88+
// PlainCoffee.java
89+
class PlainCoffee implements Coffee {
90+
91+
@Override
92+
public String getDescription() {
93+
return "PlainCoffee";
94+
}
95+
96+
@Override
97+
public int getCost() {
98+
return 10;
99+
}
100+
}
101+
```
102+
103+
```java title:CappuccinoCoffee.java
104+
// CappuccinoCoffee
105+
class CappuccinoCoffee implements Coffee {
106+
@Override
107+
public String getDescription() {
108+
return "Cappuccino";
109+
}
110+
111+
@Override
112+
public int getCost() {
113+
return 20;
114+
}
115+
}
116+
```
117+
In the code example above, the `Coffee` interface is implemented by both the **PlainCoffee** and **CappuccinoCoffee** concrete classes. For each coffee type, we override the methods and assign the **specific cost** for each coffee.
118+
```java title:CoffeeDecorator.java
119+
// CoffeeDecorator.java
120+
abstract class CoffeeDecorator implements Coffee {
121+
protected Coffee _coffee;
122+
123+
public CoffeeDecorator(Coffee coffee) {
124+
_coffee = coffee;
125+
}
126+
127+
@Override
128+
public String getDescription() {
129+
return _coffee.getDescription();
130+
}
131+
132+
@Override
133+
public int getCost() {
134+
return _coffee.getCost();
135+
}
136+
}
137+
```
138+
The `CoffeeDecorator` class is the **decorator class** that implements the `Coffee` interface to **decorate different varieties of coffee**. In our case, the **CoffeeDecorator** acts as an **add-on** (e.g., milk, sugar) that can be added to the customer's coffee if requested.
139+
```java title:MilkDecorator.java
140+
// MilkDecorator.java
141+
class MilkDecorator extends CoffeeDecorator {
142+
public MilkDecorator(Coffee decoratedcoffee) {
143+
super(decoratedcoffee);
144+
}
145+
146+
@Override
147+
public String getDescription() {
148+
return super.getDescription() + " + Milk";
149+
}
150+
151+
@Override
152+
public int getCost() {
153+
return super.getCost() + 5;
154+
}
155+
}
156+
```
157+
158+
```java title:SugarDecorator.java
159+
// SugarDecorator.java
160+
class SugarDecorator extends CoffeeDecorator {
161+
public SugarDecorator(Coffee decoratedCoffee) {
162+
super(decoratedCoffee);
163+
}
164+
165+
@Override
166+
public int getCost() {
167+
return super.getCost() + 10;
168+
}
169+
170+
@Override
171+
public String getDescription() {
172+
return super.getDescription() + " + Sugar";
173+
}
174+
}
175+
```
176+
When creating coffee objects, you can **layer decorators** as needed. For example, a **Cappuccino** with **milk and sugar** is created by **wrapping the decorators** around the base coffee object.
177+
```java title:DecoratorPattern.java
178+
public class DecoratorPattern {
179+
public static void main(String[] args) {
180+
Coffee plaincoffee = new PlainCoffee();
181+
System.out.println(plaincoffee.getDescription() + " Cost=" + plaincoffee.getCost());
182+
183+
Coffee capuccinocoffee = new CappuccinoCoffee();
184+
capuccinocoffee = new MilkDecorator(capuccinocoffee);
185+
capuccinocoffee = new SugarDecorator(capuccinocoffee);
186+
System.out.println(capuccinocoffee.getDescription() + " Cost=" + capuccinocoffee.getCost());
187+
188+
}
189+
}
190+
```
191+
**Output**:
192+
```
193+
PlainCoffee Cost=10
194+
Cappuccino + Milk + Sugar Cost=35
195+
```
196+
---
197+
## Complete Code In Java
198+
```java title:Decorator.java
199+
package decorator;
200+
201+
// Coffee.java
202+
interface Coffee {
203+
public String getDescription();
204+
public int getCost();
205+
}
206+
207+
// PlainCoffee.java
208+
class PlainCoffee implements Coffee {
209+
210+
@Override
211+
public String getDescription() {
212+
return "PlainCoffee";
213+
}
214+
215+
@Override
216+
public int getCost() {
217+
return 10;
218+
}
219+
}
220+
221+
// CappuccinoCoffee
222+
class CappuccinoCoffee implements Coffee {
223+
@Override
224+
public String getDescription() {
225+
return "Cappuccino";
226+
}
227+
228+
@Override
229+
public int getCost() {
230+
return 20;
231+
}
232+
}
233+
234+
// CoffeeDecorator.java
235+
abstract class CoffeeDecorator implements Coffee {
236+
protected Coffee _coffee;
237+
238+
public CoffeeDecorator(Coffee coffee) {
239+
_coffee = coffee;
240+
}
241+
242+
@Override
243+
public String getDescription() {
244+
return _coffee.getDescription();
245+
}
246+
247+
@Override
248+
public int getCost() {
249+
return _coffee.getCost();
250+
}
251+
}
252+
253+
// MilkDecorator.java
254+
class MilkDecorator extends CoffeeDecorator {
255+
public MilkDecorator(Coffee decoratedcoffee) {
256+
super(decoratedcoffee);
257+
}
258+
259+
@Override
260+
public String getDescription() {
261+
return super.getDescription() + " + Milk";
262+
}
263+
264+
@Override
265+
public int getCost() {
266+
return super.getCost() + 5;
267+
}
268+
}
269+
270+
// SugarDecorator.java
271+
class SugarDecorator extends CoffeeDecorator {
272+
public SugarDecorator(Coffee decoratedCoffee) {
273+
super(decoratedCoffee);
274+
}
275+
276+
@Override
277+
public int getCost() {
278+
return super.getCost() + 10;
279+
}
280+
281+
@Override
282+
public String getDescription() {
283+
return super.getDescription() + " + Sugar";
284+
}
285+
}
286+
287+
public class DecoratorPattern {
288+
public static void main(String[] args) {
289+
Coffee plaincoffee = new PlainCoffee();
290+
System.out.println(plaincoffee.getDescription() + " Cost=" + plaincoffee.getCost());
291+
292+
Coffee capuccinocoffee = new CappuccinoCoffee();
293+
capuccinocoffee = new MilkDecorator(capuccinocoffee);
294+
capuccinocoffee = new SugarDecorator(capuccinocoffee);
295+
// here we added the sugar and milk to the cappucinocoffee
296+
System.out.println(capuccinocoffee.getDescription() + " Cost=" + capuccinocoffee.getCost());
297+
298+
}
299+
}
300+
```
301+
---
302+
## Real World Example
303+
304+
The Java I/O library is a textbook example of the Decorator Pattern. The base classes such as `InputStream` and `OutputStream` are extended by concrete classes (like `FileInputStream`) and then wrapped by decorator classes that add additional functionality at runtime.
305+
306+
```java title:StreamInput.java
307+
// Base stream reading from a file
308+
InputStream fileStream = new FileInputStream("data.txt");
309+
310+
// BufferedInputStream decorates fileStream by adding buffering capability
311+
InputStream bufferedStream = new BufferedInputStream(fileStream);
312+
313+
// DataInputStream further decorates bufferedStream to allow reading primitive data types
314+
DataInputStream dataStream = new DataInputStream(bufferedStream);
315+
```
316+
---
317+
## Design Principles
318+
319+
- **Encapsulate What Varies** - Identify the parts of the code that are going to change and encapsulate them into separate class just like the Strategy Pattern.
320+
- **Favor Composition Over Inheritance** - Instead of using inheritance on extending functionality, rather use composition by delegating behavior to other objects.
321+
- **Program to Interface not Implementations** - Write code that depends on Abstractions or Interfaces rather than Concrete Classes.
322+
- **Strive for Loosely coupled design between objects that interact** - When implementing a class, avoid tightly coupled classes. Instead, use loosely coupled objects by leveraging abstractions and interfaces. This approach ensures that the class does not heavily depend on other classes.
323+
- **Classes Should be Open for Extension But closed for Modification** - Design your classes so you can extend their behavior without altering their existing, stable code.
324+
----
52.1 KB
Loading

content/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Consider an `SQLManager` class that performs CRUD operations. It has an `ILogger
118118

119119
1. [[Strategy Pattern]]
120120
2. [[Observer Pattern]]
121+
3. [[Decorator Pattern]]
121122

122123
---
123124
> [!Note]

0 commit comments

Comments
 (0)