您的位置:首页 > 编程语言 > PHP开发

PHP设计模式-观察者模式(订阅者模式)

2013-07-22 22:24 721 查看
    相信大家都用过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();


        从上面推拉模式两种代码我们可以看出,它们的区别在于,一个是在消息器中存储用户列表,另一个是在用户类中存储消息器列表,要获取和推送消息时,都要遍历这个列表进行推送和拉取消息。
   对于拉模式而言,它只需维护感兴趣的消息器列表。但它并不知道,消息器什么时候会有新消息。它只能定时地去试探是否有新消息过来。而且,每个用户都必须维护一个消息器列表。所以会重复地试探性拉数据。效率不高。但消息器服务端服务都是正常的,只要用户去取消息,那么基本上都能正常取到,因此更稳定些。故而,两种模式要根据不同的业务情况合理使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: