作者简介:

       姜海强:闷骚码农,互联网行业摸爬滚打数余载,先后担任中国体育直播TV主程、网信集团先锋支付架构师、奇虎360服务器端资深开发。热爱技术,喜欢分享,热衷领域:PHP/Golang语言、面向对象设计模式、Redis、Yaf、Yii2、微服务等。

视频课程

yaf+yar微服务-腾讯课堂
yaf+yar微服务-51CTO学院
CSDN学院

Github

个人主页
swoole-boot
roach
roach-orm

QQ群:

姜海强的QQ群

公众号:

360tryst公众号

观察者模式

观察者模式又称发布订阅模式,我们常用的redisrabbitmqkafka等都支持发布订阅,那么这个模式是怎么回事儿呢?

观察者模式主要有通知者和观察者等角色,观察者一般有多个。

我们看下面代码示例

  1. <?php
  2. /**
  3. * Class Controller
  4. * @datetime 2020/7/17 6:46 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. class Controller
  9. {
  10. /**
  11. * @var string
  12. * @datetime 2020/7/17 6:36 PM
  13. * @author roach
  14. * @email jhq0113@163.com
  15. */
  16. public $id;
  17. /**
  18. * @var string
  19. * @datetime 2020/7/17 6:39 PM
  20. * @author roach
  21. * @email jhq0113@163.com
  22. */
  23. public $actionId;
  24. /**
  25. * @datetime 2020/7/17 6:46 PM
  26. * @author roach
  27. * @email jhq0113@163.com
  28. */
  29. public function init()
  30. {
  31. $userId = time();
  32. $this->checkAccess($userId);
  33. $this->trafficLimit();
  34. }
  35. /**鉴权
  36. * @param int $userId
  37. * @return int
  38. * @datetime 2020/7/17 6:41 PM
  39. * @author roach
  40. * @email jhq0113@163.com
  41. */
  42. public function checkAccess($userId)
  43. {
  44. return $userId & 1=== 1;
  45. }
  46. /**
  47. * @return bool
  48. * @datetime 2020/7/17 6:45 PM
  49. * @author roach
  50. * @email jhq0113@163.com
  51. */
  52. public function trafficLimit()
  53. {
  54. $ip = $_SERVER['REMOTE_ADDR'];
  55. /**
  56. * @var \Redis $redis
  57. */
  58. $multi = $redis->multi(\Redis::PIPELINE);
  59. $limitKey = 'limit:'.$ip;
  60. $multi->incr($limitKey);
  61. $multi->expire($limitKey, 60);
  62. $result = $multi->exec();
  63. return $result[0] > 100;
  64. }
  65. }

以上代码是一个控制器基类,当有请求来临时会优先调用控制器的init方法,init方法会主动调用checkAccesstrafficLimit方法,一个用于鉴权,一个用于限速,这段代码在我们日常项目中也是非常常见,那么有什么问题呢?

现在我们通过开闭原则进行分析,当我们修改限速逻辑,我们需要修改trafficLimit方法,Controller发生了变化,没有对修改关闭;当我们增加其他调用时,init方法需要发生修改,没有对扩展开放,那么我们怎么修改呢?

  1. <?php
  2. /**
  3. * Interface IFilter
  4. * @datetime 2020/7/17 6:52 PM
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. interface IFilter
  9. {
  10. /**
  11. * @param array $params
  12. * @return mixed
  13. * @datetime 2020/7/17 6:51 PM
  14. * @author roach
  15. * @email jhq0113@163.com
  16. */
  17. public function filter($params = []);
  18. }
  19. /**
  20. * Class AccessFilter
  21. * @datetime 2020/7/17 6:52 PM
  22. * @author roach
  23. * @email jhq0113@163.com
  24. */
  25. class AccessFilter implements IFilter
  26. {
  27. /**
  28. * @param array $params
  29. * @return int|mixed
  30. * @datetime 2020/7/17 6:52 PM
  31. * @author roach
  32. * @email jhq0113@163.com
  33. */
  34. public function filter($params = [])
  35. {
  36. return $params['userId'] & 1=== 1;
  37. }
  38. }
  39. /**
  40. * Class LimitFilter
  41. * @datetime 2020/7/17 6:53 PM
  42. * @author roach
  43. * @email jhq0113@163.com
  44. */
  45. class LimitFilter implements IFilter
  46. {
  47. /**
  48. * @param array $params
  49. * @return bool|mixed
  50. * @datetime 2020/7/17 6:53 PM
  51. * @author roach
  52. * @email jhq0113@163.com
  53. */
  54. public function filter($params = [])
  55. {
  56. $ip = $_SERVER['REMOTE_ADDR'];
  57. /**
  58. * @var \Redis $redis
  59. */
  60. $multi = $redis->multi(\Redis::PIPELINE);
  61. $limitKey = 'limit:'.$ip;
  62. $multi->incr($limitKey);
  63. $multi->expire($limitKey, 60);
  64. $result = $multi->exec();
  65. return $result[0] > 100;
  66. }
  67. }
  68. /**
  69. * Class Controller
  70. * @datetime 2020/7/17 6:46 PM
  71. * @author roach
  72. * @email jhq0113@163.com
  73. */
  74. class Controller
  75. {
  76. /**
  77. * @var string
  78. * @datetime 2020/7/17 6:36 PM
  79. * @author roach
  80. * @email jhq0113@163.com
  81. */
  82. public $id;
  83. /**
  84. * @var string
  85. * @datetime 2020/7/17 6:39 PM
  86. * @author roach
  87. * @email jhq0113@163.com
  88. */
  89. public $actionId;
  90. /**
  91. * @var array
  92. * @datetime 2020/7/17 6:51 PM
  93. * @author roach
  94. * @email jhq0113@163.com
  95. */
  96. protected $_filters = [];
  97. /**
  98. * @datetime 2020/7/17 6:46 PM
  99. * @author roach
  100. * @email jhq0113@163.com
  101. */
  102. public function init()
  103. {
  104. $this->notify();
  105. }
  106. /**
  107. * @param IFilter $filter
  108. * @datetime 2020/7/17 6:55 PM
  109. * @author roach
  110. * @email jhq0113@163.com
  111. */
  112. public function addFilter(IFilter $filter)
  113. {
  114. array_push($this->_filters, $filter);
  115. }
  116. /**
  117. * @datetime 2020/7/17 6:55 PM
  118. * @author roach
  119. * @email jhq0113@163.com
  120. */
  121. public function notify()
  122. {
  123. $params = [
  124. 'userId' => time()
  125. ];
  126. foreach ($this->_filters as $filter) {
  127. /**
  128. * @var IFilter $filter
  129. */
  130. $filter->filter($params);
  131. }
  132. }
  133. }

修改之后,我们增加了IFilter接口(即抽象观察者角色),增加了AccessFilter(即具体观察者角色)和LimitFilter(即具体观察者角色)两个实现类,在控制器(即通知者角色)中增加了_filters属性,控制器可以通过调用addFilter方法增加观察者,通过调用notify方法通知所有观察者。

我们来分析一下修改后的优点,当我们要修改鉴权或者限速的逻辑时,控制器的代码是不需要发生修改的,属于对修改关闭;当我们要增加新的filter时,增加一个类就可以了,控制器的代码也不用发生修改,属于对扩展开放。

一个对象必须通知其他对象,而并不知道这些对象是谁。需要在系统中创建一个触发链,这时我们就可以使用观察者模式来解决。

作者开源了一个事件的实现,封装在了jhq0113/roach中,我们可以通过composer方式安装,代码开源地址如下

https://github.com/jhq0113/roach

jhq0113/roach使用简单,代码精简,整个代码库纯代码大小为60K,composer安装方式

  1. composer require jhq0113/roach

QQ群:

姜海强的QQ群

公众号:

360tryst公众号