Decorator Design pattern belongs to the category of Structural patterns.
Let’s see what is structural pattern and then we’ll explore about the Decorator design pattern
Structural patterns focus on how classes and objects are combined to create larger systems. Structural class patterns use inheritance to bring together different features or implementations.
A simple example is multiple inheritance, where a class takes features from two or more parent classes. This creates a new class that has the combined abilities of its parent classes. In java we can use Interfaces as it does not support multiple inheritance in classes.
This approach is especially helpful when you want to make separate class libraries work well together.
The Decorator pattern is a way to add extra features to objects without changing their basic structure. It works by combining objects in a step-by-step way. So that you can add as many features as you want.
For example, Think of a user interface element like a text box. A Decorator can add things like a border, a shadow, scrolling, or zooming to the text box. To add these multiple features to the text box. you just wrap one Decorator object around another, like layers. So the Decorator can add its own properties, such as drawing a border, a shadow or scrolling etc. either before or after passing the message.
Structure/UML diagram
In the UML diagram, the relationship between CarType and Decorator is both a "has-a" and an "is-a" relationship. This means that the Decorator has an association relationship with CarType ("has-a") and also inherits from it ("is-a").
The key reason is that even after adding extra features or add-ons through the Decorator, the object type should remain consistent with the original CarType.
Now lets see the code.
public interface Ride {
String getDescription();
Double getPrice();
}
public class Economy implements Ride{
private String description;
private Double price;
public Economy(Double price) {
this.description = "";
this.price = price;
}
@Override
public String getDescription() {
description = "cost of economy";
return description;
}
@Override
public Double getPrice() {
return price;
}
}
public class Premium implements Ride{
private Double price;
public Premium(double price){
this.price = price;
}
@Override
public String getDescription() {
return "cost of premium ride";
}
@Override
public Double getPrice() {
return price;
}
}
public abstract class Decorator implements Ride {
protected Ride ride;
public Decorator(Ride ride) {
this.ride = ride;
}
}
public class PriorityPickUp extends Decorator{
private Double price;
public PriorityPickUp(Ride ride, Double price) {
super(ride);
this.price =price;
}
@Override
public String getDescription() {
return ride.getDescription()+ ", priority pick ";
}
@Override
public Double getPrice() {
return ride.getPrice() + price;
}
}
public static void main(String[] args) {
Ride economy = new Economy(5d);
System.out.println(economy.getDescription());
System.out.println(economy.getPrice());
economy = new PriorityPickUp(economy, 3d);
System.out.println(economy.getDescription());
System.out.println(economy.getPrice());
Ride premium = new Premium(8d);
System.out.println(premium.getDescription());
System.out.println(premium.getPrice());
premium = new PriorityPickUp(premium, 4d);
System.out.println(premium.getDescription());
System.out.println(premium.getPrice());
}
Console output
In the conclusion,
The Decorator pattern allows for dynamically adding functionality to an object without altering its structure. It achieves this by wrapping the original object and extending its behavior. This pattern promotes flexibility and maintains the object's original type, even with added features.
Stay tuned for more on design patterns in my next blogs. 😊