The Decorator design pattern allows you to add new behavior or modify existing behavior of an object dynamically without changing its underlying implementation. It involves creating a set of decorator classes that wrap the original object and provide additional functionality.
// Component
interface Vehicle {
void accelerate();
}
// Concrete Component
final class ConstructionVehicle implements Vehicle {
@Override
public void accelerate() {
System.out.println("Construction vehicle is accelerating.");
}
}
// Decorator
abstract class VehicleDecorator implements Vehicle {
protected Vehicle decoratedVehicle;
public VehicleDecorator(Vehicle decoratedVehicle) {
this.decoratedVehicle = decoratedVehicle;
}
@Override
public void accelerate() {
decoratedVehicle.accelerate();
}
}
// Concrete Decorator
class BulldozingDecorator extends VehicleDecorator {
private String driver;
public BulldozingDecorator(Vehicle decoratedVehicle, String driver) {
super(decoratedVehicle);
this.driver = driver;
}
@Override
public void accelerate() {
super.accelerate();
System.out.println("Driver " + driver + " activated the bulldozer's Blade! pushing construction materials ...");
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
}
public class Main {
public static void main(String[] args) {
Vehicle constructionVehicle = new ConstructionVehicle();
constructionVehicle.accelerate();
/* OUTPUT
Construction vehicle is accelerating.
*/
Vehicle tomBulldozer = new BulldozingDecorator(constructionVehicle, "Tom");
tomBulldozer.accelerate();
/* OUTPUT
Construction vehicle is accelerating.
Driver Tom activated the bulldozer's Blade! pushing construction materials ...
*/
}
}
In this example, we have an interface called Vehicle
with a single method accelerate()
. The ConstructionVehicle
class is a concrete implementation of the Vehicle
interface. It represents a construction vehicle that can accelerate. The VehicleDecorator
class is an abstract class that implements the Vehicle
interface. It acts as the base class for decorators. It contains a reference to a Vehicle
object that it decorates. The accelerate()
method of VehicleDecorator
invokes the accelerate()
method of the decorated vehicle.
The BulldozingDecorator
is a specific decorator that extends the VehicleDecorator
class. It adds extra fields named driver
and also adds the functionality of bulldozing to the decorated vehicle. When the accelerate()
method of BulldozingDecorator
is called, it first invokes the accelerate()
method of the decorated vehicle and then adds the specific behavior of activating the blade and pushing construction materials.
In the Main
class, we demonstrate how the decorators can be used. We start by creating a ConstructionVehicle
object. Next, we create a bulldozer
object by wrapping the constructionVehicle
with the BulldozingDecorator
. When we call the bulldozer.accelerate()
method, it first invokes the accelerate()
method of the decorated constructionVehicle
, which outputs "Construction vehicle is accelerating." Then, it adds the behavior specific to the BulldozingDecorator
, which outputs "Blade activated! pushing construction materials ..."
By using decorators, we can dynamically add new features or modify the behavior of an object at runtime without changing its underlying implementation. In this example, the BulldozingDecorator
adds the bulldozing functionality to a construction vehicle without modifying the ConstructionVehicle
class directly. This allows for flexibility and extensibility in adding new behaviors to objects.
Use the Decorator pattern when you need to be able to assign extra behaviors to objects at runtime without breaking the code that uses these objects.
- The Decorator lets you structure your business logic into layers, create a decorator for each layer and compose objects with various combinations of this logic at runtime. The client code can treat all these objects in the same way, since they all follow a common interface.
- The
Vehicle
interface acts as the common interface. The client interacts with theVehicle
objects, whether they are the baseConstructionVehicle
or the decoratedBulldozingDecorator
.
- The
Use the pattern when it’s awkward or not possible to extend an object’s behavior using inheritance.
- Many programming languages have the
final
keyword that can be used to prevent further extension of a class. For a final class, the only way to reuse the existing behavior would be to wrap the class with your own wrapper, using the Decorator pattern.ConstructionVehicle
class is final, indicating that it cannot be directly subclassed any further to add new behaviour.- To be able to add new behaviour would require us to use the Decorator pattern to add the "bulldozing" behavior to the
ConstructionVehicle
.- The
BulldozingDecorator
acts as a wrapper around theConstructionVehicle
and provides the additional behavior of activating the blade. By using the Decorator pattern, we can extend the behavior of theConstructionVehicle
without modifying its implementation or inheriting from it directly, which would not be possible if it were marked asfinal
.
- The
- How does the Decorator pattern differs from the Proxy Pattern?
- The Decorator pattern can be used to add additional attributes (fields) to an object. Moreover, It can be used with the Builder pattern to dynamically transform an object by setting additional optional attributes (fields) via the Director's contstruct methods, effectively creating very flexible object. Hence, It allows dynamically adding new behaviors or functionalities to an object by wrapping it with a decorator object. The decorator object can modify the behavior or extend the functionality of the original object without changing its underlying structure.
- Wrapping the actual
VehicleDecorator
implementation with additional fields (driver
)
- Wrapping the actual
- On the other hand, the Proxy pattern focuses on calling the functions or methods of the underlying real class. It provides a surrogate or placeholder object that controls the access to the real object. The proxy object acts as an intermediary, allowing the client to interact with the real object indirectly. The proxy can add additional logic or perform certain operations before or after delegating the function calls to the real object.
- The Decorator pattern can be used to add additional attributes (fields) to an object. Moreover, It can be used with the Builder pattern to dynamically transform an object by setting additional optional attributes (fields) via the Director's contstruct methods, effectively creating very flexible object. Hence, It allows dynamically adding new behaviors or functionalities to an object by wrapping it with a decorator object. The decorator object can modify the behavior or extend the functionality of the original object without changing its underlying structure.