Pattern: 狀態模式
Class Diagram: 會員制
情境:平台有三種身份,分別是訪客 (guest) 、 會員 (member) 及 付費會員(premium)
- 訪客藉由註冊 (register) ,可成為會員。
- 會員藉由訂閱 (subsribe) ,可成為付費會員。
- 付費會員藉由取消訂閱 (cancelSubscription) ,可變回會員。
- 會員及付費會員藉由刪除帳號 (deleteAccount) ,可變回訪客。
| 12
 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.');
 }
 }
 
 | 
隨著功能越來越多,每次新增功能時,
我們都會有許多複雜的條件式(判斷當前用戶狀態)。
讓我們用狀態模式改善它。
需求一:切分出不同的用戶狀態
| 12
 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();
 }
 
 | 
| 12
 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.');
 }
 }
 
 | 
| 12
 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();
 }
 }
 
 | 
| 12
 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();
 }
 }
 
 | 
| 12
 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) 。
ʕ •ᴥ•ʔ:透過測試,重構這個範例時有遇到一些小困難。
大家也可以挑戰看看。