Skip to content

Commit 95b7260

Browse files
committed
新增 狀態模式 State Pattern
1 parent 506ddc3 commit 95b7260

File tree

9 files changed

+1902
-3
lines changed

9 files changed

+1902
-3
lines changed

DesignPatterns/Behavioral/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
規格模式,將邏輯條件給抽離出來,獨立成一個模組,而不是在物件內透過邏輯判斷來撰寫複雜的程式碼,簡化物件所需要實踐的邏輯,物件可以套用一個規則,也可以套用多種規則,就像大頭菜本身的價格運算是一種規格,過期後的價格運算又是另一種規格,可以把這個價格運算的邏輯抽離出來獨立成模組。
2626

2727
## [狀態模式 State Pattern](/DesignPatterns/Behavioral/StatePattern)
28-
28+
狀態模式,讓物件的狀態改變時,一同改變物件的行為模式,就像是大頭菜(Turnips)這個物件,有沒有壞掉只是一個狀態(State)來辨別,但如果壞掉了,那麼會因為狀態改變的關係,而讓大頭菜計算鈴錢價格的方式也跟著改變。
2929

3030
## [策略模式 Strategy Pattern](/DesignPatterns/Behavioral/StrategyPattern)
3131

DesignPatterns/Behavioral/StatePattern/README.md

+197-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,201 @@
11
![Banner](https://raw.githubusercontent.com/Kantai235/php-design-pattern/master/DesignPatterns/Behavioral/StatePattern/Banner.png)
22

33
# 狀態模式 State Pattern
4+
狀態模式,讓物件的狀態改變時,一同改變物件的行為模式,就像是大頭菜(Turnips)這個物件,有沒有壞掉只是一個狀態(State)來辨別,但如果壞掉了,那麼會因為狀態改變的關係,而讓大頭菜計算鈴錢價格的方式也跟著改變。
45

56
## UML
67
![UML](https://raw.githubusercontent.com/Kantai235/php-design-pattern/master/DesignPatterns/Behavioral/StatePattern/UML.png)
78

89
## 實作
10+
因為要讓大頭菜(Turnips)掛載狀態物件,所以我們要先來定義狀態,會需要提供進入到下個狀態的方法,以及 `toString` 來查看當前的狀態是什麼。
911

12+
State.php
13+
```php
14+
/**
15+
* Interface State.
16+
*/
17+
interface State
18+
{
19+
/**
20+
* @param Turnips $turnips
21+
*/
22+
public function proceedToNext(Turnips $turnips);
23+
24+
/**
25+
* @return string
26+
*/
27+
public function toString(): string;
28+
}
29+
```
30+
31+
首先是大頭菜剛建立出來的狀態,而大頭菜下個狀態是壞掉的狀態,所以在 `proceedToNext` 方法我們要將大頭菜(Turnips)來去賦予下個階段的狀態。
32+
33+
StateCreated.php
34+
```php
35+
/**
36+
* Class StateCreated.
37+
*/
38+
class StateCreated implements State
39+
{
40+
/**
41+
* @param Turnips $turnips
42+
*/
43+
public function proceedToNext(Turnips $turnips)
44+
{
45+
$turnips->setState(new StateSpoiled());
46+
}
47+
48+
/**
49+
* @return string
50+
*/
51+
public function toString(): string
52+
{
53+
return 'created';
54+
}
55+
}
56+
```
57+
58+
再來是壞掉的大頭菜狀態,這個階段已經是最終階段了,所以在 `proceedToNext` 的部分則是不實作任何事。
59+
60+
StateSpoiled.php
61+
```php
62+
/**
63+
* Class StateSpoiled.
64+
*/
65+
class StateSpoiled implements State
66+
{
67+
/**
68+
* @param Turnips $turnips
69+
*/
70+
public function proceedToNext(Turnips $turnips)
71+
{
72+
// there is nothing more to do
73+
}
74+
75+
/**
76+
* @return string
77+
*/
78+
public function toString(): string
79+
{
80+
return 'spoiled';
81+
}
82+
}
83+
```
84+
85+
最後我們要實作大頭菜(Turnips),除了要儲存鈴錢價格(Price)、數量(Count)以外,還要儲存當前的狀態(State),這個狀態會在一開始被建立時就擁有,並且會在執行 `proceedToNext` 時被變更,最後提供計算鈴錢總價格的 `calculatePrice` 方法,並且根據當前的狀態(State)來切換計算模式。
86+
87+
Turnips.php
88+
```php
89+
/**
90+
* Class Turnips.
91+
*/
92+
class Turnips
93+
{
94+
/**
95+
* @var State
96+
*/
97+
protected State $state;
98+
99+
/**
100+
* @var int
101+
*/
102+
protected int $price;
103+
104+
/**
105+
* @var int
106+
*/
107+
protected int $count;
108+
109+
/**
110+
* Turnips constructor.
111+
*
112+
* @param int $price
113+
* @param int $count
114+
*/
115+
public function __construct(int $price, int $count)
116+
{
117+
$this->price = $price;
118+
$this->count = $count;
119+
}
120+
121+
/**
122+
* @return Turnips
123+
*/
124+
public static function create(int $price, int $count): Turnips
125+
{
126+
$turnips = new self($price, $count);
127+
$turnips->state = new StateCreated();
128+
129+
return $turnips;
130+
}
131+
132+
/**
133+
* @param State $state
134+
*/
135+
public function setState(State $state)
136+
{
137+
$this->state = $state;
138+
}
139+
140+
/**
141+
* @return void
142+
*/
143+
public function proceedToNext()
144+
{
145+
$this->state->proceedToNext($this);
146+
}
147+
148+
/**
149+
* @return string
150+
*/
151+
public function toString()
152+
{
153+
return $this->state->toString();
154+
}
155+
156+
/**
157+
* @return int
158+
*/
159+
public function calculatePrice(): int
160+
{
161+
switch ($this->toString()) {
162+
case 'created':
163+
return $this->price * $this->count;
164+
165+
case 'spoiled':
166+
return 0;
167+
}
168+
}
169+
}
170+
```
10171

11172
## 測試
173+
最後我們要對狀態模式做測試,測試的項目很簡單,就是建立一個大頭菜物件,這時候是健康的大頭菜,所以應該要可以得知大頭菜現在的狀態是剛建立的 `created` 以及正常計算鈴錢價格,再來把大頭菜切換為下個狀態,也就是壞掉的大頭菜,這時候應該要獲得壞掉的狀態 `spoiled` 以及計算出 0 鈴錢。
174+
175+
StatePatternTest.php
176+
```php
177+
/**
178+
* Class StatePatternTest.
179+
*/
180+
class StatePatternTest extends TestCase
181+
{
182+
/**
183+
* @test
184+
*/
185+
public function test_state_spoiled()
186+
{
187+
$turnips = Turnips::create(100, 40);
12188

189+
$this->assertSame('created', $turnips->toString());
190+
$this->assertEquals(4000, $turnips->calculatePrice());
191+
192+
$turnips->proceedToNext();
193+
194+
$this->assertSame('spoiled', $turnips->toString());
195+
$this->assertEquals(0, $turnips->calculatePrice());
196+
}
197+
}
198+
```
13199

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

@@ -20,6 +206,15 @@ PHPUnit Pretty Result Printer 0.28.0 by Codedungeon and contributors.
20206
PHPUnit 9.2.6 by Sebastian Bergmann and contributors.
21207
22208
209+
==> ...fResponsibilitiesTest ✔ ✔ ✔
210+
==> CommandPatternTest ✔
211+
==> IteratorPatternTest ✔ ✔ ✔ ✔
212+
==> MediatorPatternTest ✔ ✔ ✔
213+
==> MementoPatternTest ✔
214+
==> NullObjectPatternTest ✔ ✔ ✔ ✔
215+
==> ObserverPatternTest ✔
216+
==> SpecificationPatternTest ✔ ✔ ✔ ✔
217+
==> StatePatternTest ✔
23218
==> AbstractFactoryTest ✔ ✔ ✔ ✔
24219
==> BuilderPatternTest ✔ ✔ ✔ ✔
25220
==> FactoryMethodTest ✔ ✔ ✔ ✔
@@ -40,9 +235,9 @@ PHPUnit 9.2.6 by Sebastian Bergmann and contributors.
40235
==> ProxyPatternTest ✔ ✔
41236
==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔
42237
43-
Time: 00:00.037, Memory: 6.00 MB
238+
Time: 00:00.100, Memory: 8.00 MB
44239
45-
OK (51 tests, 116 assertions)
240+
OK (73 tests, 145 assertions)
46241
```
47242

48243
## 完整程式碼
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace DesignPatterns\Behavioral\StatePattern;
4+
5+
/**
6+
* Interface State.
7+
*/
8+
interface State
9+
{
10+
/**
11+
* @param Turnips $turnips
12+
*/
13+
public function proceedToNext(Turnips $turnips);
14+
15+
/**
16+
* @return string
17+
*/
18+
public function toString(): string;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace DesignPatterns\Behavioral\StatePattern;
4+
5+
/**
6+
* Class StateCreated.
7+
*/
8+
class StateCreated implements State
9+
{
10+
/**
11+
* @param Turnips $turnips
12+
*/
13+
public function proceedToNext(Turnips $turnips)
14+
{
15+
$turnips->setState(new StateSpoiled());
16+
}
17+
18+
/**
19+
* @return string
20+
*/
21+
public function toString(): string
22+
{
23+
return 'created';
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace DesignPatterns\Behavioral\StatePattern;
4+
5+
/**
6+
* Class StateSpoiled.
7+
*/
8+
class StateSpoiled implements State
9+
{
10+
/**
11+
* @param Turnips $turnips
12+
*/
13+
public function proceedToNext(Turnips $turnips)
14+
{
15+
// there is nothing more to do
16+
}
17+
18+
/**
19+
* @return string
20+
*/
21+
public function toString(): string
22+
{
23+
return 'spoiled';
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace DesignPatterns\Behavioral\StatePattern;
4+
5+
/**
6+
* Class Turnips.
7+
*/
8+
class Turnips
9+
{
10+
/**
11+
* @var State
12+
*/
13+
protected State $state;
14+
15+
/**
16+
* @var int
17+
*/
18+
protected int $price;
19+
20+
/**
21+
* @var int
22+
*/
23+
protected int $count;
24+
25+
/**
26+
* Turnips constructor.
27+
*
28+
* @param int $price
29+
* @param int $count
30+
*/
31+
public function __construct(int $price, int $count)
32+
{
33+
$this->price = $price;
34+
$this->count = $count;
35+
}
36+
37+
/**
38+
* @return Turnips
39+
*/
40+
public static function create(int $price, int $count): Turnips
41+
{
42+
$turnips = new self($price, $count);
43+
$turnips->state = new StateCreated();
44+
45+
return $turnips;
46+
}
47+
48+
/**
49+
* @param State $state
50+
*/
51+
public function setState(State $state)
52+
{
53+
$this->state = $state;
54+
}
55+
56+
/**
57+
* @return void
58+
*/
59+
public function proceedToNext()
60+
{
61+
$this->state->proceedToNext($this);
62+
}
63+
64+
/**
65+
* @return string
66+
*/
67+
public function toString()
68+
{
69+
return $this->state->toString();
70+
}
71+
72+
/**
73+
* @return int
74+
*/
75+
public function calculatePrice(): int
76+
{
77+
switch ($this->toString()) {
78+
case 'created':
79+
return $this->price * $this->count;
80+
81+
case 'spoiled':
82+
return 0;
83+
}
84+
}
85+
}
104 KB
Loading

0 commit comments

Comments
 (0)