1
1
![ Banner] ( https://raw.githubusercontent.com/Kantai235/php-design-pattern/master/DesignPatterns/Behavioral/StatePattern/Banner.png )
2
2
3
3
# 狀態模式 State Pattern
4
+ 狀態模式,讓物件的狀態改變時,一同改變物件的行為模式,就像是大頭菜(Turnips)這個物件,有沒有壞掉只是一個狀態(State)來辨別,但如果壞掉了,那麼會因為狀態改變的關係,而讓大頭菜計算鈴錢價格的方式也跟著改變。
4
5
5
6
## UML
6
7
![ UML] ( https://raw.githubusercontent.com/Kantai235/php-design-pattern/master/DesignPatterns/Behavioral/StatePattern/UML.png )
7
8
8
9
## 實作
10
+ 因為要讓大頭菜(Turnips)掛載狀態物件,所以我們要先來定義狀態,會需要提供進入到下個狀態的方法,以及 ` toString ` 來查看當前的狀態是什麼。
9
11
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
+ ```
10
171
11
172
## 測試
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);
12
188
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
+ ```
13
199
14
200
最後測試的執行結果會獲得如下:
15
201
@@ -20,6 +206,15 @@ PHPUnit Pretty Result Printer 0.28.0 by Codedungeon and contributors.
20
206
PHPUnit 9.2.6 by Sebastian Bergmann and contributors.
21
207
22
208
209
+ ==> ...fResponsibilitiesTest ✔ ✔ ✔
210
+ ==> CommandPatternTest ✔
211
+ ==> IteratorPatternTest ✔ ✔ ✔ ✔
212
+ ==> MediatorPatternTest ✔ ✔ ✔
213
+ ==> MementoPatternTest ✔
214
+ ==> NullObjectPatternTest ✔ ✔ ✔ ✔
215
+ ==> ObserverPatternTest ✔
216
+ ==> SpecificationPatternTest ✔ ✔ ✔ ✔
217
+ ==> StatePatternTest ✔
23
218
==> AbstractFactoryTest ✔ ✔ ✔ ✔
24
219
==> BuilderPatternTest ✔ ✔ ✔ ✔
25
220
==> FactoryMethodTest ✔ ✔ ✔ ✔
@@ -40,9 +235,9 @@ PHPUnit 9.2.6 by Sebastian Bergmann and contributors.
40
235
==> ProxyPatternTest ✔ ✔
41
236
==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔
42
237
43
- Time: 00:00.037 , Memory: 6 .00 MB
238
+ Time: 00:00.100 , Memory: 8 .00 MB
44
239
45
- OK (51 tests, 116 assertions)
240
+ OK (73 tests, 145 assertions)
46
241
```
47
242
48
243
## 完整程式碼
0 commit comments