修飾模式 Decorator Pattern

修飾模式,或者稱裝飾者模式,為物件動態增加新的方法,就想像你最初的大頭菜沒有想過他會壞掉,某天突然覺得讓大頭菜壞掉好像很好玩,但你不能把整個大頭菜砍掉重練,所以你希望可以不改變既有的大頭菜,在大頭菜額外再套上新的功能,那就是壞掉。

UML

UML

實作

首先我們會需要定義出最初始大頭菜的介面,以及其最初的功能。

TurnipsInterface.php

1
2
3
4
5
6
7
/**
* Interface TurnipsInterface.
*/
interface TurnipsInterface
{
public function calculatePrice(): int;
}

TurnipsService.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
/**
* Class TurnipsService.
*/
class TurnipsService implements TurnipsInterface
{
/**
* @var int
*/
protected $price;

/**
* @var int
*/
protected $count;

/**
* TurnipsService constructor.
*
* @param int $price
* @param int $count
*/
public function __construct(int $price, int $count)
{
$this->price = $price;
$this->count = $count;
}

/**
* @return int
*/
public function calculatePrice(): int
{
return $this->price * $this->count;
}
}

再來我們需要建立裝飾器(Decorator),來修飾我們後來添加的壞掉功能,並且把最初始的大頭菜丟進去,讓大頭菜直接壞掉,沒有壞掉就拿石頭丟他!

TurnipsDecorator.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Class TurnipsDecorator.
*/
abstract class TurnipsDecorator implements TurnipsInterface
{
/**
* @var TurnipsInterface
*/
protected $turnips;

/**
* TurnipsDecorator constructor.
*
* @param TurnipsInterface $turnips
*/
public function __construct(TurnipsInterface $turnips)
{
$this->turnips = $turnips;
}
}

Turnips.php

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Class Turnips.
*/
class Turnips extends TurnipsDecorator
{
/**
* @return int
*/
public function calculatePrice(): int
{
return $this->turnips->calculatePrice();
}
}

Spoiled.php

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Class Spoiled.
*/
class Spoiled extends TurnipsDecorator
{
/**
* @return int
*/
public function calculatePrice(): int
{
return 0;
}
}

測試

完成了大頭菜裝飾器之後,我們接下來要驗證前面所寫的裝飾器是否能夠正常運行,因此我們有幾個重要項目要來做測試:

  1. 測試正常的大頭菜是否可以賣鈴錢。
  2. 測試壞掉的大頭菜是否沒辦法賣鈴錢。
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
/**
* Class DecoratorPatternTest.
*/
class DecoratorPatternTest extends TestCase
{
/**
* 測試正常的大頭菜是否可以賣鈴錢。
*
* @test
*/
public function test_turnips()
{
$service = new TurnipsService(100, 40);
$turnips = new Turnips($service);

$this->assertEquals(4000, $turnips->calculatePrice());
}

/**
* 測試壞掉的大頭菜是否沒辦法賣鈴錢。
*
* @test
*/
public function test_spoiled()
{
$service = new TurnipsService(100, 40);
$turnips = new Spoiled($service);

$this->assertEquals(0, $turnips->calculatePrice());
}
}

最後測試的執行結果會獲得如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PHPUnit Pretty Result Printer 0.28.0 by Codedungeon and contributors.
==> Configuration: ~/php-design-pattern/vendor/codedungeon/phpunit-result-printer/src/phpunit-printer.yml

PHPUnit 9.2.6 by Sebastian Bergmann and contributors.


==> AbstractFactoryTest ✔ ✔ ✔ ✔
==> BuilderPatternTest ✔ ✔ ✔ ✔
==> FactoryMethodTest ✔ ✔ ✔ ✔
==> PoolPatternTest ✔ ✔
==> PrototypePatternTest ✔ ✔
==> SimpleFactoryTest ✔ ✔ ✔ ✔
==> SingletonPatternTest ✔
==> StaticFactoryTest ✔ ✔ ✔ ✔ ✔
==> AdapterPatternTest ✔ ✔
==> BridgePatternTest ✔ ✔ ✔
==> CompositePatternTest ✔ ✔ ✔
==> DataMapperTest ✔ ✔
==> DecoratorPatternTest ✔ ✔

Time: 00:00.050, Memory: 6.00 MB

OK (41 tests, 88 assertions)

完整程式碼

設計模式不難,找回快樂而已,以大頭菜為例。

參考文獻