在之前的文章《一起聊聊PHP中的單例模式》中我們介紹了PHP中的單例模式,下面本篇文章帶大家了解一下PHP設(shè)計(jì)模式中的狀態(tài)模式
狀態(tài)模式從字面上其實(shí)并不是很好理解。這里的狀態(tài)是什么意思呢?保存狀態(tài)?那不就是備忘錄模式了。其實(shí),這里的狀態(tài)是類的狀態(tài),通過改變類的某個(gè)狀態(tài),讓這個(gè)類感覺像是換了一個(gè)類一樣。說(shuō)起來(lái)有點(diǎn)拗口吧,先學(xué)習(xí)概念之后再看。
Gof類圖及解釋
GoF定義:允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為。對(duì)象看起來(lái)似乎修改了它的類
GoF類圖
代碼實(shí)現(xiàn)
class Context { private $state; public function SetState(State $state): void { $this->state = $state; } public function Request(): void { $this->state = $this->state->Handle(); } }
一個(gè)上下文類,也可以看作是目標(biāo)類,它的內(nèi)部有一個(gè)狀態(tài)對(duì)象。當(dāng)調(diào)用Request()的時(shí)候,去調(diào)用狀態(tài)類的Handle()方法。目的是當(dāng)前上下文類狀態(tài)的變化都由外部的這個(gè)狀態(tài)類來(lái)進(jìn)行操縱。
interface State { public function Handle(): State; } class ConcreteStateA implements State { public function Handle(): State { echo '當(dāng)前是A狀態(tài)', PHP_EOL; return new ConcreteStateB(); } } class ConcreteStateB implements State { public function Handle(): State { echo '當(dāng)前是B狀態(tài)', PHP_EOL; return new ConcreteStateA(); } }
抽象狀態(tài)接口及兩個(gè)具體實(shí)現(xiàn)。這兩個(gè)具體實(shí)現(xiàn)實(shí)際上是在相互調(diào)用。實(shí)現(xiàn)的效果就是上下文類每調(diào)用一次Request()方法,內(nèi)部的狀態(tài)類就變成別一個(gè)狀態(tài)。就像一個(gè)開關(guān),在打開與關(guān)閉中來(lái)回切換一樣。
$c = new Context(); $stateA = new ConcreteStateA(); $c->SetState($stateA); $c->Request(); $c->Request(); $c->Request(); $c->Request();
客戶端的實(shí)現(xiàn),實(shí)例化上下文對(duì)象并設(shè)置初始的狀態(tài),然后通過不停的調(diào)用Request()對(duì)象來(lái)實(shí)現(xiàn)開關(guān)狀態(tài)的切換。
- 看出門道了嘛?這里把狀態(tài)的變化給封裝到外部的實(shí)現(xiàn)類去了,并不是這個(gè)上下文或者目標(biāo)類內(nèi)部來(lái)進(jìn)行狀態(tài)的切換了
- 那么狀態(tài)模式的意義呢?這個(gè)默認(rèn)類圖的例子過于簡(jiǎn)單,其實(shí)狀態(tài)模式的真正目的是為了解決復(fù)雜的if嵌套問題的,把復(fù)雜的if嵌套條件放到一個(gè)個(gè)的外部狀態(tài)類中去判斷,在后面的實(shí)例中我們會(huì)看到
- 適用于:一個(gè)對(duì)象的行為取決于它的狀態(tài),并且它的必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變自己的行為;一個(gè)操作中含有大量的多分支條件語(yǔ)句,且這些分支依賴于該對(duì)象的狀態(tài);
- 狀態(tài)模式的特點(diǎn)是:它將與特定狀態(tài)相關(guān)的行為局部化;它使得狀態(tài)轉(zhuǎn)換顯式化;State對(duì)象可以被共享;
- 常見于訂單系統(tǒng)、會(huì)員系統(tǒng)、OA系統(tǒng)中,也就是流程中會(huì)出現(xiàn)各種狀態(tài)變化的情況,都可以使用狀態(tài)模式來(lái)進(jìn)行整體的設(shè)計(jì)與架構(gòu)
我們的手機(jī)系統(tǒng)內(nèi)定制了自己的商城系統(tǒng),可以在手機(jī)上方便的下單購(gòu)買我們的商品。一個(gè)訂單(Context)會(huì)有多種狀態(tài)(State),比如未支付、已支付、訂單完成、訂單退款等等一大堆狀態(tài)。我們把這些狀態(tài)都放在了對(duì)應(yīng)的狀態(tài)類里去實(shí)現(xiàn),不同的狀態(tài)類都會(huì)再去調(diào)用該狀態(tài)下一步的動(dòng)作,比如已支付后就等待收貨、退款后就等待買家填寫物流單號(hào)等,這樣,狀態(tài)模式就在我們的商城中被靈活的運(yùn)用起來(lái)咯??!
完整代碼:https://github.com/zhangyue0503/designpatterns-php/blob/master/22.state/source/state.php
實(shí)例
通常的商城應(yīng)用中都會(huì)有會(huì)員體系的存在,一般等級(jí)越高的會(huì)員可以享受的折扣也會(huì)越多,這個(gè)時(shí)候,運(yùn)用狀態(tài)模式就能很輕松的獲得會(huì)員的等級(jí)折扣。當(dāng)然,最主要的是,使用狀態(tài)模式可以在需要添加或者刪除會(huì)員等級(jí)時(shí)只添加對(duì)應(yīng)的會(huì)員折扣狀態(tài)子類就可以了。其他業(yè)務(wù)代碼都不需要變動(dòng),我們一起來(lái)看看具體實(shí)現(xiàn)吧!
會(huì)員折扣圖
完整源碼:https://github.com/zhangyue0503/designpatterns-php/blob/master/22.state/source/state-member.php
<?php class Member { private $state; private $score; public function SetState($state) { $this->state = $state; } public function SetScore($score) { $this->score = $score; } public function GetScore() { return $this->score; } public function discount() { return $this->state->discount($this); } } interface State { public function discount($member); } class PlatinumMemeberState implements State { public function discount($member) { if ($member->GetScore() >= 1000) { return 0.80; } else { $member->SetState(new GoldMemberState()); return $member->discount(); } } } class GoldMemberState implements State { public function discount($member) { if ($member->GetScore() >= 800) { return 0.85; } else { $member->SetState(new SilverMemberState()); return $member->discount(); } } } class SilverMemberState implements State { public function discount($member) { if ($member->GetScore() >= 500) { return 0.90; } else { $member->SetState(new GeneralMemberState()); return $member->discount(); } } } class GeneralMemberState implements State { public function discount($member) { return 0.95; } } $m = new Member(); $m->SetState(new PlatinumMemeberState()); $m->SetScore(1200); echo '當(dāng)前會(huì)員' . $m->GetScore() . '積分,折扣為:' . $m->discount(), PHP_EOL; $m->SetScore(990); echo '當(dāng)前會(huì)員' . $m->GetScore() . '積分,折扣為:' . $m->discount(), PHP_EOL; $m->SetScore(660); echo '當(dāng)前會(huì)員' . $m->GetScore() . '積分,折扣為:' . $m->discount(), PHP_EOL; $m->SetScore(10); echo '當(dāng)前會(huì)員' . $m->GetScore() . '積分,折扣為:' . $m->discount(), PHP_EOL;
說(shuō)明
- 如果不使用狀態(tài)模式,在Member的discount()方法中,我們可能需要寫很多層if…else…判斷條件
- 同時(shí),這也帶來(lái)了方法體會(huì)越來(lái)越長(zhǎng),越來(lái)越難以維護(hù)的問題
- 狀態(tài)模式正是為了解決這個(gè)問題而存在的
- 當(dāng)discount()行為的結(jié)果依賴于Member對(duì)象本身的狀態(tài)(會(huì)員分)時(shí),狀態(tài)模式就是最佳的選擇了,也就是上面所說(shuō)的一個(gè)對(duì)象的行為取決于它的狀態(tài)
原文地址:https://juejin.cn/post/6844903991562731534
作者:硬核項(xiàng)目經(jīng)理
推薦學(xué)習(xí):《PHP視頻教程》