Decorator Pattern

Purpose

To dynamically add new functionality to class instances.

UML


Code

Booking


namespace DesignPatterns\Structural\Decorator;

interface Booking
{
    public function calculatePrice(): int;
    public function getDescription(): string;
}

DoubleRoomBooking


namespace DesignPatterns\Structural\Decorator;

class DoubleRoomBooking implements Booking
{
    public function calculatePrice(): int
    {
        return 40;
    }

    public function getDescription(): string
    {
        return 'double room';
    }
}

BookingDecorator


namespace DesignPatterns\Structural\Decorator;

abstract class BookingDecorator implements Booking
{
    public function __construct(protected Booking $booking)
    {

    }
}

ExtraBed


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


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


// 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());
}

Ready to bring your vision to life?

We believe in excellence, empathy, integrity, and transparency throughout the process. Our goal is to build fast, responsive websites that not only perform but also reflect your values and vision.