相信大家都用过QQ(没用过QQ的大叔不要扔我),而且大家都很讨厌QQ的小弹窗,不时地就会跳出一个小窗口,真心烦人。那么如果我们是腾讯消息推送的服务端开发人员。如果要用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();
从上面推拉模式两种代码我们可以看出,它们的区别在于,一个是在消息器中存储用户列表,另一个是在用户类中存储消息器列表,要获取和推送消息时,都要遍历这个列表进行推送和拉取消息。
对于拉模式而言,它只需维护感兴趣的消息器列表。但它并不知道,消息器什么时候会有新消息。它只能定时地去试探是否有新消息过来。而且,每个用户都必须维护一个消息器列表。所以会重复地试探性拉数据。效率不高。但消息器服务端服务都是正常的,只要用户去取消息,那么基本上都能正常取到,因此更稳定些。故而,两种模式要根据不同的业务情况合理使用。