How does a decorator pattern work?
The decorator pattern is a structural design pattern that allows you to add new functionality to an object dynamically without modifying its structure. It is a form of the open/closed principle, which states that software entities should be open for extension but closed for modification. This pattern is particularly useful when you want to enhance the behavior of an object at runtime without altering its core functionality.
In the decorator pattern, a decorator is a class that wraps another class and adds new functionality to it. The decorator extends the object it decorates, and it is used to add features to the object without changing its interface. This allows for flexible and reusable code, as decorators can be combined and stacked to create complex behaviors.
To understand how the decorator pattern works, let’s consider a simple example. Imagine you have a class called `Coffee`, which represents a coffee shop’s product. Initially, the `Coffee` class only supports basic flavors like “Espresso” and “Cappuccino”. However, you want to add new features, such as “Milk”, “Sugar”, and “Whipped Cream”, without modifying the `Coffee` class itself.
Here’s how the decorator pattern can be applied to this scenario:
The `Coffee` class can be defined as follows:
“`python
class Coffee:
def __init__(self):
self._description = “Basic Coffee”
def get_description(self):
return self._description
def cost(self):
return 1.0
“`
Now, let’s create a decorator class called `Decorator` that will add new features to the `Coffee` object:
“`python
class Decorator(Coffee):
def __init__(self, coffee):
self._coffee = coffee
def get_description(self):
return self._coffee.get_description() + ” with ” + self._description
def cost(self):
return self._coffee.cost() + self._price_increase
“`
To add a new feature to the `Coffee` object, you can create a subclass of `Decorator` and implement the desired functionality:
“`python
class Milk(Decorator):
def __init__(self, coffee):
super().__init__(coffee)
self._description = “Milk”
self._price_increase = 0.5
class Sugar(Decorator):
def __init__(self, coffee):
super().__init__(coffee)
self._description = “Sugar”
self._price_increase = 0.3
class WhippedCream(Decorator):
def __init__(self, coffee):
super().__init__(coffee)
self._description = “Whipped Cream”
self._price_increase = 0.8
“`
Now, you can create a `Coffee` object and add features using decorators:
“`python
coffee = Coffee()
coffee_with_milk = Milk(coffee)
coffee_with_sugar = Sugar(coffee_with_milk)
coffee_with_whipped_cream = WhippedCream(coffee_with_sugar)
print(coffee_with_whipped_cream.get_description()) Output: Basic Coffee with Milk with Sugar with Whipped Cream
print(coffee_with_whipped_cream.cost()) Output: 2.6
“`
In this example, the decorator pattern allows you to dynamically add new features to the `Coffee` object without modifying its structure. The `Decorator` class acts as a wrapper around the `Coffee` object, and you can easily combine multiple decorators to create complex behaviors. This pattern provides a flexible and reusable way to extend the functionality of objects at runtime.