PHP設計模式-觀察者模式(訂閱者模式)
方案一。被動推送方式
我們採用推的方式來接收訊息。也說說,由服務端向各位使用者直接推送訊息。我們考慮地簡單一點,畢竟我們只是學習設計模式嘛。首先,我們需要有一個使用者類。可以展示推送的訊息。其次,我們需要一個訊息推送器的類,有個推送訊息的方法,它可以指定把訊息推給哪個使用者。
class User { //展示推送過來的訊息 public function showMessage($msg) { echo "Message: $msg".PHP_EOL; } } class Messager { //推送訊息給某使用者 public function push(User $user, String $msg) { $user->showMessage($msg); } }
好,需要的東西都有了。那麼,該把訊息推給誰呢?嗯,有了,我先把要推送訊息的使用者都存在一個列表中。當有新訊息需要推送的時候,我直接推送給每個使用者就可以了。於是,把推送器稍做修改如下:
class Messager { //用來儲存需要推送訊息的使用者 protected $_users = array(); //推送訊息給某使用者 public function push(User $user, String $msg) { $user->showMessage($msg); } //將訊息推送給所有使用者 public function pushAll($msg) { foreach ($this->_users as $user) { $this->push($user, $msg); } } }
推送器可以通過pushAll()方法給所有使用者推送訊息了。我們發現,我們還缺少一些東西,對,我們需要一些把使用者從推送器新增或刪除的方法。同時,我們需要注意一下,使用者需要一個唯一的標識來區分是哪個使用者。於是在使用者類中新增一個使用者標識userId,並且,建立使用者時需指定這個Id.而推送器在新增和刪除使用者時,都會使用這個Id。程式碼如下:
class User { //使用者的唯一標識 private $_userId; //使用者初始化時需指定ID public function __construct($userId) { $this->_userId = $userId; } //獲取使用者ID public function getUserId() { return $this->_userId; } //展示推送過來的訊息 public function showMessage($msg) { echo "Message: $msg".PHP_EOL; } } class Messager { //用來儲存需要推送訊息的使用者 protected $_users = array(); //推送訊息給某使用者 public function push(User $user, $msg) { $user->showMessage($msg); } //將訊息推送給所有使用者 public function pushAll($msg) { foreach ($this->_users as $user) { $this->push($user, $msg); } } //新增使用者 public function addUser(User $user) { $this->_users[$user->getUserId()] = $user; } //刪除使用者 public function delUser($userId) { unset($this->_users[$userId]); } //清除所有使用者 public function clearUsers() { $this->_users = array(); } }
推送方式的程式碼完成了。現在我們開始測試一下,給兩個使用者傳送訊息。
$messager = new Messager();
$user1 = new User(1);
$user2 = new User(2);
$messager->addUser($user1);
$messager->addUser($user2);
$messager->pushAll("test");
我們看到,推送器建立了一個數組,用來存所有需要推送訊息的使用者。其實我們可以把這些使用者叫做觀察者,或者叫訂閱者。把它們加入到這個陣列中,就表示他們對這個訊息器中的訊息很關心。那麼訊息推送器在有新訊息的時候,就遍歷這個陣列,把訊息推送出去。
方案二。拉取方式
如果這個使用者量很大呢?陣列存不下怎麼辦?如果使用者不線上,訊息也會推送不出去。那這種推的方式就不適用了。
我們就會想了,那我們改成拉的模式吧。這樣我們不用維護大的使用者列表,也不用關心使用者在不線上了。當用戶上線後,可以連線到訊息器上獲取訊息。
要實現拉的模式。首先使用者還是要有顯示訊息的方法,但不同之處在於,它需要知道訊息器是什麼?那麼訊息器就簡單了,要有一個可以獲取訊息的方法。並且有一個訊息器的唯一標識(messageId)如下:
class User
{
//使用者的唯一標識
private $_userId;
//使用者初始化時需指定ID
public function __construct($userId)
{
$this->_userId = $userId;
}
//獲取使用者ID
public function getUserId()
{
return $this->_userId;
}
//展示訊息推送器的訊息,它需要傳遞進來一個訊息器
public function showMessage(Messager $messager)
{
echo "Message".$messager->getMessage();
}
}
class Messager
{
//訊息器Id
private $_messagerId;
//訊息器初始化時需要指定Id
public function __construct($messagerId)
{
$this->_messagerId = $messagerId;
}
//獲取Messager的Id
public function getMessagerId()
{
return $this->_messagerId;
}
//從遠端獲取訊息資訊
public function getMessage()
{
//此處實現從服務端拿訊息 ...
return "遠端拿到的訊息";
}
}
在上面,使用者可以支援從一個訊息器拉訊息。那麼,如果有多個訊息器呢?我們需要在使用者中儲存一個推送器的列表,然後定時遍歷推送器列表,獲取訊息。於是最終程式碼如下:
class User
{
//使用者的唯一標識
private $_userId;
//推送器列表
protected $_messagers;
//使用者初始化時需指定ID
public function __construct($userId)
{
$this->_userId = $userId;
}
//獲取使用者ID
public function getUserId()
{
return $this->_userId;
}
//新增訊息器
public function addMessager(Messager $messager)
{
$this->_messagers[$messager->getMessagerId()] = $messager;
}
//移除訊息器
public function removeMessager($messagerId)
{
unset($this->_messagers[$messagerId]);
}
//清除訊息器
public function clearMessagers()
{
$this->_messagers = array();
}
//顯示所有訊息並顯示
public function showAllMessage()
{
foreach ($this->_messagers as $messager) {
$this->showMessage($messager);
}
}
//展示訊息推送器的訊息,它需要傳遞進來一個訊息器
public function showMessage(Messager $messager)
{
echo "Message".$messager->getMessage();
}
}
class Messager
{
//訊息器Id
private $_messagerId;
//訊息器初始化時需要指定Id
public function __construct($messagerId)
{
$this->_messagerId = $messagerId;
}
//獲取Messager的Id
public function getMessagerId()
{
return $this->_messagerId;
}
//從遠端獲取訊息資訊
public function getMessage()
{
//此處實現從服務端拿訊息 ...
return "遠端拿到的訊息";
}
}
拉模式的程式碼我們也完成了。現在來測試一下效果:
$user = new User(1);
$messager1 = new Messager(1);
$messager2 = new Messager(2);
$user->addMessager($messager1);
$user->addMessager($messager2);
$user->showAllMessage();
從上面推拉模式兩種程式碼我們可以看出,它們的區別在於,一個是在訊息器中儲存使用者列表,另一個是在使用者類中儲存訊息器列表,要獲取和推送訊息時,都要遍歷這個列表進行推送和拉取訊息。
對於拉模式而言,它只需維護感興趣的訊息器列表。但它並不知道,訊息器什麼時候會有新訊息。它只能定時地去試探是否有新訊息過來。而且,每個使用者都必須維護一個訊息器列表。所以會重複地試探性拉資料。效率不高。但訊息器服務端服務都是正常的,只要使用者去取訊息,那麼基本上都能正常取到,因此更穩定些。故而,兩種模式要根據不同的業務情況合理使用。