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.
20206PHPUnit 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## 完整程式碼
0 commit comments