Explore the Decorator Pattern in PHP to dynamically add responsibilities to objects without altering their structure. Learn implementation techniques, use cases, and best practices.
The Decorator Pattern is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. This pattern is particularly useful in PHP for enhancing the functionality of objects in a flexible and reusable manner.
The primary intent of the Decorator Pattern is to add new responsibilities to objects dynamically without altering their structure. This is achieved by creating a set of decorator classes that are used to wrap concrete components.
Use the Decorator Pattern when:
To implement the Decorator Pattern in PHP, follow these steps:
Let’s consider a coffee shop scenario where we want to dynamically add ingredients to a coffee order.
1<?php
2
3// Component Interface
4interface Coffee {
5 public function getCost(): float;
6 public function getDescription(): string;
7}
8
9// Concrete Component
10class SimpleCoffee implements Coffee {
11 public function getCost(): float {
12 return 5.0;
13 }
14
15 public function getDescription(): string {
16 return "Simple Coffee";
17 }
18}
19
20// Decorator
21abstract class CoffeeDecorator implements Coffee {
22 protected $coffee;
23
24 public function __construct(Coffee $coffee) {
25 $this->coffee = $coffee;
26 }
27
28 public function getCost(): float {
29 return $this->coffee->getCost();
30 }
31
32 public function getDescription(): string {
33 return $this->coffee->getDescription();
34 }
35}
36
37// Concrete Decorators
38class MilkDecorator extends CoffeeDecorator {
39 public function getCost(): float {
40 return $this->coffee->getCost() + 1.5;
41 }
42
43 public function getDescription(): string {
44 return $this->coffee->getDescription() . ", Milk";
45 }
46}
47
48class SugarDecorator extends CoffeeDecorator {
49 public function getCost(): float {
50 return $this->coffee->getCost() + 0.5;
51 }
52
53 public function getDescription(): string {
54 return $this->coffee->getDescription() . ", Sugar";
55 }
56}
57
58// Client Code
59$coffee = new SimpleCoffee();
60echo $coffee->getDescription() . " costs $" . $coffee->getCost() . "\n";
61
62$coffeeWithMilk = new MilkDecorator($coffee);
63echo $coffeeWithMilk->getDescription() . " costs $" . $coffeeWithMilk->getCost() . "\n";
64
65$coffeeWithMilkAndSugar = new SugarDecorator($coffeeWithMilk);
66echo $coffeeWithMilkAndSugar->getDescription() . " costs $" . $coffeeWithMilkAndSugar->getCost() . "\n";
67
68?>
Below is a class diagram representing the Decorator Pattern:
classDiagram
class Coffee {
<<interface>>
+getCost() float
+getDescription() string
}
class SimpleCoffee {
+getCost() float
+getDescription() string
}
class CoffeeDecorator {
<<abstract>>
-Coffee coffee
+getCost() float
+getDescription() string
}
class MilkDecorator {
+getCost() float
+getDescription() string
}
class SugarDecorator {
+getCost() float
+getDescription() string
}
Coffee <|-- SimpleCoffee
Coffee <|-- CoffeeDecorator
CoffeeDecorator <|-- MilkDecorator
CoffeeDecorator <|-- SugarDecorator
Coffee o-- CoffeeDecorator
The Decorator Pattern is widely used in scenarios where you need to add functionalities like logging, validation, or formatting to objects. Here are some common use cases:
Consider a scenario where we want to add logging to a data processing component.
1<?php
2
3// Component Interface
4interface DataProcessor {
5 public function process(string $data): string;
6}
7
8// Concrete Component
9class SimpleDataProcessor implements DataProcessor {
10 public function process(string $data): string {
11 return strtoupper($data);
12 }
13}
14
15// Decorator
16abstract class DataProcessorDecorator implements DataProcessor {
17 protected $processor;
18
19 public function __construct(DataProcessor $processor) {
20 $this->processor = $processor;
21 }
22
23 public function process(string $data): string {
24 return $this->processor->process($data);
25 }
26}
27
28// Concrete Decorator
29class LoggingDecorator extends DataProcessorDecorator {
30 public function process(string $data): string {
31 echo "Logging: Processing data...\n";
32 return $this->processor->process($data);
33 }
34}
35
36// Client Code
37$processor = new SimpleDataProcessor();
38$loggedProcessor = new LoggingDecorator($processor);
39
40echo $loggedProcessor->process("Hello World");
41
42?>
When using the Decorator Pattern, consider the following:
PHP offers several features that make implementing the Decorator Pattern straightforward:
The Decorator Pattern is often confused with the Proxy Pattern. While both involve wrapping objects, the key difference is in their intent:
Experiment with the Decorator Pattern by modifying the code examples provided. Try adding new decorators, such as a WhippedCreamDecorator or a CaramelDecorator, to the coffee example. Observe how easily you can extend the functionality without altering the existing code.
Remember, mastering design patterns like the Decorator Pattern is a journey. As you continue to explore and experiment, you’ll discover new ways to enhance your PHP applications. Keep learning, stay curious, and enjoy the process!