经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
每天一个设计模式之订阅-发布模式
来源:cnblogs  作者:GodBMW  时间:2018/12/11 9:25:29  对本文有异议

博主按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用javascript靠这吃饭)和python纯粹喜欢)两种语言实现。诚然,每种设计模式都有多种实现方式,但此小册只记录最直截了当的实现方式 :)

0. 项目地址

1. 什么是“订阅-发布模式”?

订阅-发布模式定义了对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都可以得到通知。

了解过事件机制或者函数式编程的朋友,应该会体会到“订阅-发布模式”所带来的“时间解耦”和“空间解耦”的优点。借助函数式编程中闭包和回调的概念,可以很优雅地实现这种设计模式。

2. “订阅-发布模式” vs 观察者模式

订阅-发布模式和观察者模式概念相似,但在订阅-发布模式中,订阅者和发布者之间多了一层中间件:一个被抽象出来的信息调度中心。

但其实没有必要太深究 2 者区别,因为《Head First 设计模式》这本经典书都写了:发布+订阅=观察者模式其核心思想是状态改变和发布通知。在此基础上,根据语言特性,进行实现即可。

3. 代码实现

3.1 python3 实现

python 中我们定义一个事件类Event, 并且为它提供 事件监听函数、(事件完成后)触发函数,以及事件移除函数。任何类都可以通过继承这个通用事件类,来实现“订阅-发布”功能。

  1. class Event:
  2. def __init__(self):
  3. self.client_list = {}
  4. def listen(self, key, fn):
  5. if key not in self.client_list:
  6. self.client_list[key] = []
  7. self.client_list[key].append(fn)
  8. def trigger(self, *args, **kwargs):
  9. fns = self.client_list[args[0]]
  10. length = len(fns)
  11. if not fns or length == 0:
  12. return False
  13. for fn in fns:
  14. fn(*args[1:], **kwargs)
  15. return False
  16. def remove(self, key, fn):
  17. if key not in self.client_list or not fn:
  18. return False
  19. fns = self.client_list[key]
  20. length = len(fns)
  21. for _fn in fns:
  22. if _fn == fn:
  23. fns.remove(_fn)
  24. return True
  25. # 借助继承为对象安装 发布-订阅 功能
  26. class SalesOffice(Event):
  27. def __init__(self):
  28. super().__init__()
  29. # 根据自己需求定义一个函数:供事件处理完后调用
  30. def handle_event(event_name):
  31. def _handle_event(*args, **kwargs):
  32. print("Price is", *args, "at", event_name)
  33. return _handle_event
  34. if __name__ == "__main__":
  35. # 创建2个回调函数
  36. fn1 = handle_event("event01")
  37. fn2 = handle_event("event02")
  38. sales_office = SalesOffice()
  39. # 订阅event01 和 event02 这2个事件,并且绑定相关的 完成后的函数
  40. sales_office.listen("event01", fn1)
  41. sales_office.listen("event02", fn2)
  42. # 当两个事件完成时候,触发前几行绑定的相关函数
  43. sales_office.trigger("event01", 1000)
  44. sales_office.trigger("event02", 2000)
  45. sales_office.remove("event01", fn1)
  46. # 打印:False
  47. print(sales_office.trigger("event01", 1000))

3.2 ES6 实现

JS 中一般用事件模型来代替传统的发布-订阅模式。任何一个对象的原型链被指向Event的时候,这个对象便可以绑定自定义事件和对应的回调函数。

  1. const Event = {
  2. clientList: {},
  3. // 绑定事件监听
  4. listen(key, fn) {
  5. if (!this.clientList[key]) {
  6. this.clientList[key] = [];
  7. }
  8. this.clientList[key].push(fn);
  9. return true;
  10. },
  11. // 触发对应事件
  12. trigger() {
  13. const key = Array.prototype.shift.apply(arguments),
  14. fns = this.clientList[key];
  15. if (!fns || fns.length === 0) {
  16. return false;
  17. }
  18. for (let fn of fns) {
  19. fn.apply(null, arguments);
  20. }
  21. return true;
  22. },
  23. // 移除相关事件
  24. remove(key, fn) {
  25. let fns = this.clientList[key];
  26. // 如果之前没有绑定事件
  27. // 或者没有指明要移除的事件
  28. // 直接返回
  29. if (!fns || !fn) {
  30. return false;
  31. }
  32. // 反向遍历移除置指定事件函数
  33. for (let l = fns.length - 1; l >= 0; l--) {
  34. let _fn = fns[l];
  35. if (_fn === fn) {
  36. fns.splice(l, 1);
  37. }
  38. }
  39. return true;
  40. }
  41. };
  42. // 为对象动态安装 发布-订阅 功能
  43. const installEvent = obj => {
  44. for (let key in Event) {
  45. obj[key] = Event[key];
  46. }
  47. };
  48. let salesOffices = {};
  49. installEvent(salesOffices);
  50. // 绑定自定义事件和回调函数
  51. salesOffices.listen(
  52. "event01",
  53. (fn1 = price => {
  54. console.log("Price is", price, "at event01");
  55. })
  56. );
  57. salesOffices.listen(
  58. "event02",
  59. (fn2 = price => {
  60. console.log("Price is", price, "at event02");
  61. })
  62. );
  63. salesOffices.trigger("event01", 1000);
  64. salesOffices.trigger("event02", 2000);
  65. salesOffices.remove("event01", fn1);
  66. // 输出: false
  67. // 说明删除成功
  68. console.log(salesOffices.trigger("event01", 1000));

4. 参考

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号