範例:會員制(狀態模式)

Pattern: 狀態模式

Class Diagram: 會員制


情境:平台有三種身份,分別是訪客 (guest)會員 (member)付費會員(premium)

  1. 訪客藉由註冊 (register) ,可成為會員。
  2. 會員藉由訂閱 (subsribe) ,可成為付費會員。
  3. 付費會員藉由取消訂閱 (cancelSubscription) ,可變回會員。
  4. 會員及付費會員藉由刪除帳號 (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
{
/**
* @var string
*/
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;
}

/**
* @param string $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
{
/**
* @var Program
*/
protected $program;

/**
* @var string
*/
const LICENCE = 'guest';

/**
* @param Program $program
*/
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
{
/**
* @var Program
*/
protected $program;

/**
* @var string
*/
const LICENCE = 'member';

/**
* @param Program $program
*/
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
{
/**
* @var Program
*/
protected $program;

/**
* @var string
*/
const LICENCE = 'premium';

/**
* @param Program $program
*/
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
{
/**
* @var string
*/
protected $license;

/**
* @var MemberState
*/
protected $memberState;

/**
* @var GuestState
*/
protected $guestState;

/**
* @var PremiumState
*/
protected $premiumState;

/**
* @var UserState
*/
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)

ʕ •ᴥ•ʔ:透過測試,重構這個範例時有遇到一些小困難。
大家也可以挑戰看看。