Development Blog
Collection of development articles I've put together...
Decorator Pattern
To dynamically add new functionality to class instances.
1
Likes
Stuart Todd
•
2 years ago
•
Design Patterns: Structural
Purpose
The purpose of the design pattern
To dynamically add new functionality to class instances.
UML
UML design pattern diagram
Code
Code snippets
Booking
Interface for all the booking options.
namespace DesignPatterns\Structural\Decorator;
interface Booking
{
public function calculatePrice(): int;
public function getDescription(): string;
}
DoubleRoomBooking
The base option, other options will be dynamically added to this based on requirements.
namespace DesignPatterns\Structural\Decorator;
class DoubleRoomBooking implements Booking
{
public function calculatePrice(): int
{
return 40;
}
public function getDescription(): string
{
return 'double room';
}
}
BookingDecorator
Sub options extend this abstract BookingDecorator, which takes a class which implements the Booking Interface as a construct parameter.
namespace DesignPatterns\Structural\Decorator;
abstract class BookingDecorator implements Booking
{
public function __construct(protected Booking $booking)
{
}
}
ExtraBed
Extra Bed is a sub option which can potentially be attached to the base option. It extends the abstract BookingDecorator.
namespace DesignPatterns\Structural\Decorator;
class ExtraBed extends BookingDecorator
{
private const PRICE = 30;
public function calculatePrice(): int
{
return $this->booking->calculatePrice() + self::PRICE;
}
public function getDescription(): string
{
return $this->booking->getDescription() . ' with extra bed';
}
}
WiFi
WiFi is a sub option which can potentially be attached to the base option. It extends the abstract BookingDecorator.
namespace DesignPatterns\Structural\Decorator;
class WiFi extends BookingDecorator
{
private const PRICE = 2;
public function calculatePrice(): int
{
return $this->booking->calculatePrice() + self::PRICE;
}
public function getDescription(): string
{
return $this->booking->getDescription() . ' with wifi';
}
}
Tests
Some examples of how to implement this pattern
// Customer wants a double room. Price is £40 and the description is ‘double room’.
public function testCanCalculatePriceForBasicDoubleRoomBooking()
{
$booking = new DoubleRoomBooking();
$this->assertSame(40, $booking->calculatePrice());
$this->assertSame('double room', $booking->getDescription());
}
// Customer wants a double room with Wifi. Price is now £42 with the description ‘double room with wifi’.
public function testCanCalculatePriceForDoubleRoomBookingWithWiFi()
{
$booking = new DoubleRoomBooking();
$booking = new WiFi($booking);
$this->assertSame(42, $booking->calculatePrice());
$this->assertSame('double room with wifi', $booking->getDescription());
}
// Customer wants a double room with Wifi and an extra bed. Price is now £72 with the description ‘double room with wifi with extra bed’.
public function testCanCalculatePriceForDoubleRoomBookingWithWiFiAndExtraBed()
{
$booking = new DoubleRoomBooking();
$booking = new WiFi($booking);
$booking = new ExtraBed($booking);
$this->assertSame(72, $booking->calculatePrice());
$this->assertSame('double room with wifi with extra bed', $booking->getDescription());
}