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

    相信大家都用过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();

        从上面推拉模式两种代码我们可以看出,它们的区别在于,一个是在消息器中存储用户列表,另一个是在用户类中存储消息器列表,要获取和推送消息时,都要遍历这个列表进行推送和拉取消息。

   对于拉模式而言,它只需维护感兴趣的消息器列表。但它并不知道,消息器什么时候会有新消息。它只能定时地去试探是否有新消息过来。而且,每个用户都必须维护一个消息器列表。所以会重复地试探性拉数据。效率不高。但消息器服务端服务都是正常的,只要用户去取消息,那么基本上都能正常取到,因此更稳定些。故而,两种模式要根据不同的业务情况合理使用。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值