Pattern: 狀態模式
Class Diagram: 會員制
情境:平台有三種身份,分別是訪客 (guest) 、 會員 (member) 及 付費會員(premium)
- 訪客藉由註冊 (register) ,可成為會員。
- 會員藉由訂閱 (subsribe) ,可成為付費會員。
- 付費會員藉由取消訂閱 (cancelSubscription) ,可變回會員。
- 會員及付費會員藉由刪除帳號 (deleteAccount) ,可變回訪客。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| <?php
namespace App\StatePattern\Youtube;
use Exception;
class Program {
protected $license;
public function __construct() { $this->setLicense('guest'); }
public function register() { if ($this->license == 'premium') { return; }
$this->license = 'member'; }
public function getLicense() { return $this->license; }
public function setLicense($license) { $this->license = $license; }
public function subscribe() { if ($this->license == 'premium') { return; }
if ($this->license == 'member') { $this->license = 'premium'; return; }
throw new Exception('You need to be a member before subscribing.'); }
public function cancelSubscription() { if ($this->license == 'premium') { $this->license = 'member'; return; }
throw new Exception('Sorry, you have not subscribed.'); }
public function deleteAccount() { if ($this->license == 'member' || $this->license == 'premium') { $this->license = 'guest'; return; }
throw new Exception('You need to be a member before deleting account.'); } }
|
隨著功能越來越多,每次新增功能時,
我們都會有許多複雜的條件式(判斷當前用戶狀態)。
讓我們用狀態模式改善它。
需求一:切分出不同的用戶狀態
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
namespace App\StatePattern\Youtube\State;
abstract class UserState { const LICENCE = 'undefined user';
public function getLicense() { return $this::LICENCE; }
abstract function register();
abstract function subscribe();
abstract function cancelSubscription();
abstract function deleteAccount(); }
|
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 36 37 38 39 40 41 42 43 44 45 46 47 48
| <?php
namespace App\StatePattern\Youtube\State;
use App\StatePattern\Youtube\Program; use Exception; use App\StatePattern\Youtube\State\UserState;
class GuestState extends UserState {
protected $program;
const LICENCE = 'guest';
public function __construct(Program $program) { $this->program = $program; }
public function register() { $this->program->setMemberState(); }
public function subscribe() { throw new Exception('You need to be a member before subscribing.'); }
public function cancelSubscription() { throw new Exception('Sorry, you have not subscribed.'); }
public function deleteAccount() { throw new Exception('You need to be a member before deleting account.'); } }
|
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 36 37 38 39 40 41 42 43 44 45 46 47 48
| <?php
namespace App\StatePattern\Youtube\State;
use App\StatePattern\Youtube\Program; use Exception; use App\StatePattern\Youtube\State\UserState;
class MemberState extends UserState {
protected $program;
const LICENCE = 'member';
public function __construct(Program $program) { $this->program = $program; }
public function register() { return; }
public function subscribe() { $this->program->setPremiumState(); }
public function cancelSubscription() { throw new Exception('Sorry, you have not subscribed.'); }
public function deleteAccount() { $this->program->setGuestState(); } }
|
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 36 37 38 39 40 41 42 43 44 45 46 47
| <?php
namespace App\StatePattern\Youtube\State;
use App\StatePattern\Youtube\Program; use App\StatePattern\Youtube\State\UserState;
class PremiumState extends UserState {
protected $program;
const LICENCE = 'premium';
public function __construct(Program $program) { $this->program = $program; }
public function register() { return; }
public function subscribe() { return; }
public function cancelSubscription() { $this->program->setMemberState(); }
public function deleteAccount() { $this->program->setGuestState(); } }
|
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| <?php
namespace App\StatePattern\Youtube;
use App\StatePattern\Youtube\State\MemberState; use App\StatePattern\Youtube\State\GuestState; use App\StatePattern\Youtube\State\PremiumState; use App\StatePattern\Youtube\State\UserState;
class Program {
protected $license;
protected $memberState;
protected $guestState;
protected $premiumState;
protected $state;
public function __construct() { $this->memberState = new MemberState($this); $this->guestState = new GuestState($this); $this->premiumState = new PremiumState($this);
$this->setGuestState(); }
public function register() { $this->state->register(); }
public function getLicense() { return $this->state->getLicense(); }
public function subscribe() { $this->state->subscribe(); }
public function cancelSubscription() { $this->state->cancelSubscription(); }
public function setGuestState() { $this->state = $this->guestState; }
public function setMemberState() { $this->state = $this->memberState; }
public function setPremiumState() { $this->state = $this->premiumState; }
public function getState() { return $this->state; }
public function deleteAccount() { $this->state->deleteAccount(); } }
|
[單一職責原則]
我們將情境類別與狀態類別視作兩種不同的職責。
透過委派來實現不同狀態下的行為。
[開放封閉原則]
修改既有狀態類別的行為,不會影響到全部的既有狀態類別。
(新增狀態類別時,可能會影響到)
[介面隔離原則]
情境類別依賴於抽象的用戶狀態 (User State) 。
不同的狀態類別實作抽象的用戶狀態 (User State) 。
ʕ •ᴥ•ʔ:透過測試,重構這個範例時有遇到一些小困難。
大家也可以挑戰看看。