装饰器模式

目的

能够为类实例动态添加新功能。

使用场景

  • Web 服务层:REST 服务的 JSON 和 XML 装饰器(当然,在这种情况下,只允许使用其中的一种)

UML 类图

装饰器模式类图

代码

Booking.php

1
2
3
4
5
6
7
8
9
10
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Decorator;

interface Booking
{
public function calculatePrice(): int;

public function getDescription(): string;
}

BookingDecorator.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Decorator;

abstract class BookingDecorator implements Booking
{
protected Booking $booking;

public function __construct(Booking $booking)
{
$this->booking = $booking;
}
}

DoubleRoomBooking.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Decorator;

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

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

ExtraBed.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php declare(strict_types = 1);

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.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php declare(strict_types = 1);

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/DecoratorTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Decorator\Tests;

use DesignPatterns\Structural\Decorator\DoubleRoomBooking;
use DesignPatterns\Structural\Decorator\ExtraBed;
use DesignPatterns\Structural\Decorator\WiFi;
use PHPUnit\Framework\TestCase;

class DecoratorTest extends TestCase
{
public function testCanCalculatePriceForBasicDoubleRoomBooking()
{
$booking = new DoubleRoomBooking();

$this->assertSame(40, $booking->calculatePrice());
$this->assertSame('double room', $booking->getDescription());
}

public function testCanCalculatePriceForDoubleRoomBookingWithWiFi()
{
$booking = new DoubleRoomBooking();
$booking = new WiFi($booking);

$this->assertSame(42, $booking->calculatePrice());
$this->assertSame('double room with wifi', $booking->getDescription());
}

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