本文同步更新於blog
情境:平台有三種身份,分別是訪客 (guest)、會員 (member)及付費會員(premium)
<?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.');
}
}
隨著功能越來越多,每次新增功能時,
我們都會有許多複雜的條件式(判斷當前用戶狀態)。
讓我們用狀態模式改善它。
需求一:切分出不同的用戶狀態
<?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();
}
<?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.');
}
}
<?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();
}
}
<?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();
}
}
<?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)。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:透過測試,重構這個範例時有遇到一些小困難。
大家也可以挑戰看看。