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

相信大家都用过QQ(没用过QQ的大叔不要扔我),而且大家都很讨厌QQ的小弹窗,不时地就会跳出一个小窗口,真心烦人。那么如果我们是腾讯消息推送的服务端开发人员。如果要用PHP来实现这种消息发送那么如果做到呢?

    

    方案一。被动推送方式

   我们采用推的方式来接收消息。也说说,由服务端向各位用户直接推送消息。我们考虑地简单一点,毕竟我们只是学习设计模式嘛。首先,我们需要有一个用户类。可以展示推送的消息。其次,我们需要一个消息推送器的类,有个推送消息的方法,它可以指定把消息推给哪个用户。

 
  1. class User

  2. {

  3.  
  4. //展示推送过来的消息

  5. public function showMessage($msg)

  6. {

  7. echo "Message: $msg".PHP_EOL;

  8. }

  9.  
  10. }

  11.  
  12. class Messager

  13. {

  14.  
  15. //推送消息给某用户

  16. public function push(User $user, String $msg)

  17. {

  18. $user->showMessage($msg);

  19. }

  20.  
  21. }

    好,需要的东西都有了。那么,该把消息推给谁呢?嗯,有了,我先把要推送消息的用户都存在一个列表中。当有新消息需要推送的时候,我直接推送给每个用户就可以了。于是,把推送器稍做修改如下:

 
  1. class Messager

  2. {

  3.  
  4. //用来存储需要推送消息的用户

  5. protected $_users = array();

  6.  
  7. //推送消息给某用户

  8. public function push(User $user, String $msg)

  9. {

  10. $user->showMessage($msg);

  11. }

  12.  
  13. //将消息推送给所有用户

  14. public function pushAll($msg)

  15. {

  16. foreach ($this->_users as $user) {

  17. $this->push($user, $msg);

  18. }

  19. }

  20. }

   推送器可以通过pushAll()方法给所有用户推送消息了。我们发现,我们还缺少一些东西,对,我们需要一些把用户从推送器添加或删除的方法。同时,我们需要注意一下,用户需要一个唯一的标识来区分是哪个用户。于是在用户类中添加一个用户标识userId,并且,创建用户时需指定这个Id.而推送器在添加和删除用户时,都会使用这个Id。代码如下:

 
  1. class User

  2. {

  3.  
  4. //用户的唯一标识

  5. private $_userId;

  6.  
  7. //用户初始化时需指定ID

  8. public function __construct($userId)

  9. {

  10. $this->_userId = $userId;

  11. }

  12.  
  13. //获取用户ID

  14. public function getUserId()

  15. {

  16. return $this->_userId;

  17. }

  18.  
  19. //展示推送过来的消息

  20. public function showMessage($msg)

  21. {

  22. echo "Message: $msg".PHP_EOL;

  23. }

  24. }

  25.  
  26. class Messager

  27. {

  28.  
  29. //用来存储需要推送消息的用户

  30. protected $_users = array();

  31.  
  32. //推送消息给某用户

  33. public function push(User $user, $msg)

  34. {

  35. $user->showMessage($msg);

  36. }

  37.  
  38. //将消息推送给所有用户

  39. public function pushAll($msg)

  40. {

  41. foreach ($this->_users as $user) {

  42. $this->push($user, $msg);

  43. }

  44. }

  45.  
  46. //添加用户

  47. public function addUser(User $user)

  48. {

  49. $this->_users[$user->getUserId()] = $user;

  50. }

  51.  
  52. //删除用户

  53. public function delUser($userId)

  54. {

  55. unset($this->_users[$userId]);

  56. }

  57.  
  58. //清除所有用户

  59. public function clearUsers()

  60. {

  61. $this->_users = array();

  62. }

  63. }

        推送方式的代码完成了。现在我们开始测试一下,给两个用户发送消息。

 
  1. $messager = new Messager();

  2. $user1 = new User(1);

  3. $user2 = new User(2);

  4. $messager->addUser($user1);

  5. $messager->addUser($user2);

  6. $messager->pushAll("test");

    我们看到,推送器创建了一个数组,用来存所有需要推送消息的用户。其实我们可以把这些用户叫做观察者,或者叫订阅者。把它们加入到这个数组中,就表示他们对这个消息器中的消息很关心。那么消息推送器在有新消息的时候,就遍历这个数组,把消息推送出去。

    

        方案二。拉取方式

    

   如果这个用户量很大呢?数组存不下怎么办?如果用户不在线,消息也会推送不出去。那这种推的方式就不适用了。

我们就会想了,那我们改成拉的模式吧。这样我们不用维护大的用户列表,也不用关心用户在不在线了。当用户上线后,可以连接到消息器上获取消息。

   要实现拉的模式。首先用户还是要有显示消息的方法,但不同之处在于,它需要知道消息器是什么?那么消息器就简单了,要有一个可以获取消息的方法。并且有一个消息器的唯一标识(messageId)如下:

 
  1. class User

  2. {

  3.  
  4. //用户的唯一标识

  5. private $_userId;

  6.  
  7. //用户初始化时需指定ID

  8. public function __construct($userId)

  9. {

  10. $this->_userId = $userId;

  11. }

  12.  
  13. //获取用户ID

  14. public function getUserId()

  15. {

  16. return $this->_userId;

  17. }

  18.  
  19. //展示消息推送器的消息,它需要传递进来一个消息器

  20. public function showMessage(Messager $messager)

  21. {

  22. echo "Message".$messager->getMessage();

  23. }

  24. }

  25.  
  26. class Messager

  27. {

  28.  
  29. //消息器Id

  30. private $_messagerId;

  31.  
  32. //消息器初始化时需要指定Id

  33. public function __construct($messagerId)

  34. {

  35. $this->_messagerId = $messagerId;

  36. }

  37.  
  38. //获取Messager的Id

  39. public function getMessagerId()

  40. {

  41. return $this->_messagerId;

  42. }

  43.  
  44. //从远端获取消息信息

  45. public function getMessage()

  46. {

  47. //此处实现从服务端拿消息 ...

  48. return "远端拿到的消息";

  49. }

  50. }

    在上面,用户可以支持从一个消息器拉消息。那么,如果有多个消息器呢?我们需要在用户中保存一个推送器的列表,然后定时遍历推送器列表,获取消息。于是最终代码如下:

 
  1. class User

  2. {

  3.  
  4. //用户的唯一标识

  5. private $_userId;

  6.  
  7. //推送器列表

  8. protected $_messagers;

  9.  
  10. //用户初始化时需指定ID

  11. public function __construct($userId)

  12. {

  13. $this->_userId = $userId;

  14. }

  15.  
  16. //获取用户ID

  17. public function getUserId()

  18. {

  19. return $this->_userId;

  20. }

  21.  
  22. //添加消息器

  23. public function addMessager(Messager $messager)

  24. {

  25. $this->_messagers[$messager->getMessagerId()] = $messager;

  26. }

  27.  
  28. //移除消息器

  29. public function removeMessager($messagerId)

  30. {

  31. unset($this->_messagers[$messagerId]);

  32. }

  33.  
  34. //清除消息器

  35. public function clearMessagers()

  36. {

  37. $this->_messagers = array();

  38. }

  39.  
  40. //显示所有消息并显示

  41. public function showAllMessage()

  42. {

  43. foreach ($this->_messagers as $messager) {

  44. $this->showMessage($messager);

  45. }

  46. }

  47.  
  48. //展示消息推送器的消息,它需要传递进来一个消息器

  49. public function showMessage(Messager $messager)

  50. {

  51. echo "Message".$messager->getMessage();

  52. }

  53. }

  54.  
  55. class Messager

  56. {

  57.  
  58. //消息器Id

  59. private $_messagerId;

  60.  
  61. //消息器初始化时需要指定Id

  62. public function __construct($messagerId)

  63. {

  64. $this->_messagerId = $messagerId;

  65. }

  66.  
  67. //获取Messager的Id

  68. public function getMessagerId()

  69. {

  70. return $this->_messagerId;

  71. }

  72.  
  73.  
  74. //从远端获取消息信息

  75. public function getMessage()

  76. {

  77. //此处实现从服务端拿消息 ...

  78. return "远端拿到的消息";

  79. }

  80. }

        拉模式的代码我们也完成了。现在来测试一下效果:

 
  1. $user = new User(1);

  2. $messager1 = new Messager(1);

  3. $messager2 = new Messager(2);

  4. $user->addMessager($messager1);

  5. $user->addMessager($messager2);

  6. $user->showAllMessage();

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

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

猜你喜欢

转载自blog.csdn.net/zhao_teng/article/details/87689850