Browse Source

first commit

zhangjingming9 1 năm trước cách đây
commit
09f2bcfc09
100 tập tin đã thay đổi với 24898 bổ sung0 xóa
  1. 97 0
      INTRODUCTION.md
  2. 116 0
      README.md
  3. 4 0
      book.json
  4. 86 0
      docs/160463.md
  5. 85 0
      docs/160981.md
  6. 130 0
      docs/160985.md
  7. 114 0
      docs/160991.md
  8. 330 0
      docs/161114.md
  9. 96 0
      docs/161575.md
  10. 204 0
      docs/161587.md
  11. 251 0
      docs/164907.md
  12. 312 0
      docs/165103.md
  13. 193 0
      docs/165114.md
  14. 431 0
      docs/166698.md
  15. 194 0
      docs/169593.md
  16. 152 0
      docs/169600.md
  17. 343 0
      docs/169631.md
  18. 101 0
      docs/171760.md
  19. 277 0
      docs/171767.md
  20. 182 0
      docs/171771.md
  21. 76 0
      docs/172690.md
  22. 342 0
      docs/176075.md
  23. 154 0
      docs/177110.md
  24. 400 0
      docs/177442.md
  25. 243 0
      docs/177444.md
  26. 199 0
      docs/177448.md
  27. 338 0
      docs/179607.md
  28. 300 0
      docs/179615.md
  29. 233 0
      docs/179644.md
  30. 349 0
      docs/179673.md
  31. 96 0
      docs/179679.md
  32. 107 0
      docs/182001.md
  33. 145 0
      docs/183007.md
  34. 335 0
      docs/185684.md
  35. 476 0
      docs/186691.md
  36. 111 0
      docs/187761.md
  37. 164 0
      docs/188622.md
  38. 109 0
      docs/188857.md
  39. 434 0
      docs/188882.md
  40. 146 0
      docs/190979.md
  41. 396 0
      docs/191621.md
  42. 364 0
      docs/191642.md
  43. 279 0
      docs/191647.md
  44. 76 0
      docs/192789.md
  45. 335 0
      docs/193093.md
  46. 442 0
      docs/193221.md
  47. 476 0
      docs/193555.md
  48. 365 0
      docs/194035.md
  49. 273 0
      docs/194068.md
  50. 206 0
      docs/196790.md
  51. 392 0
      docs/197254.md
  52. 320 0
      docs/198614.md
  53. 279 0
      docs/199674.md
  54. 313 0
      docs/200786.md
  55. 263 0
      docs/201823.md
  56. 278 0
      docs/202786.md
  57. 254 0
      docs/204845.md
  58. 417 0
      docs/205912.md
  59. 79 0
      docs/206409.md
  60. 342 0
      docs/207456.md
  61. 285 0
      docs/208572.md
  62. 231 0
      docs/209343.md
  63. 196 0
      docs/210170.md
  64. 392 0
      docs/211239.md
  65. 335 0
      docs/212049.md
  66. 350 0
      docs/212802.md
  67. 246 0
      docs/214014.md
  68. 258 0
      docs/215132.md
  69. 395 0
      docs/216278.md
  70. 227 0
      docs/217395.md
  71. 505 0
      docs/218375.md
  72. 204 0
      docs/219290.md
  73. 350 0
      docs/219964.md
  74. 272 0
      docs/221269.md
  75. 398 0
      docs/221852.md
  76. 249 0
      docs/222762.md
  77. 201 0
      docs/223947.md
  78. 138 0
      docs/224549.md
  79. 359 0
      docs/225904.md
  80. 193 0
      docs/226710.md
  81. 229 0
      docs/227452.md
  82. 86 0
      docs/229157.md
  83. 356 0
      docs/229996.md
  84. 233 0
      docs/230708.md
  85. 96 0
      docs/232061.md
  86. 66 0
      docs/232427.md
  87. 100 0
      docs/232687.md
  88. 88 0
      docs/233742.md
  89. 333 0
      docs/234758.md
  90. 268 0
      docs/235334.md
  91. 107 0
      docs/236935.md
  92. 310 0
      docs/237810.md
  93. 486 0
      docs/238418.md
  94. 126 0
      docs/239239.md
  95. 331 0
      docs/240147.md
  96. 679 0
      docs/240971.md
  97. 116 0
      docs/242314.md
  98. 88 0
      docs/243175.md
  99. 412 0
      docs/243961.md
  100. 0 0
      docs/245022.md

+ 97 - 0
INTRODUCTION.md

@@ -0,0 +1,97 @@
+# 设计模式之美
+
+## 【立省 ¥120丨限时拼团】
+
+限时拼团+口令「666design」立省 ¥120  
+口令仅前 200 人有效
+
+  
+
+## 你将获得
+
+*   23 种设计模式与范式实战精讲;
+*   200+ 真实案例分析设计与实现;
+*   顶尖互联网公司的编程经验分享;
+*   应对设计模式面试的思路与技巧。
+
+  
+
+## 讲师介绍
+
+王争,《数据结构与算法之美》作者,前Google工程师,从事Google翻译、知识图谱等相关系统的开发。曾任某金融公司核心系统资深系统架构师,负责公司核心业务的架构设计和开发工作。工作十多年,干过架构、做过产品、带过团队、创过业,最后发现还是最喜欢写代码,始终没有脱离编码第一线。
+
+  
+
+## 课程介绍
+
+设计模式对你来说,应该不陌生。在面试中,经常会被问到;在工作中,有时候也会用到。一些设计模式书籍,比如大名鼎鼎的GoF的《设计模式》、通俗易懂的《Head First设计模式》,估计你也都研读过。那你是否觉得自己已经掌握了设计模式呢?是否思考过怎么才算真正掌握了设计模式呢?是熟练掌握每种设计模式的原理和代码实现吗?
+
+搞懂23种经典的设计模式,并不是件难事。你随便找本书看看就差不多了。难的是如何不生搬硬套、恰到好处地将其应用到实际的项目中。即便如此,这也并不是我们的最终目标。毕竟设计模式只是解决问题的一个方法,我们最终的目标还是要写出高质量的代码。
+
+单纯学习设计模式,并不能让你写出更好的代码。这就像单纯地了解编程语言的语法,也不能算是会写代码一样。单纯看书,对于设计模式的掌握、代码能力的锻炼,你只能达到10%,剩下的90%还是要靠在实战中刻意练习。
+
+而大部分工程师可能都是偏业务开发,在平时做的项目中,很少有高密度地使用各种设计模式的机会,所以这方面的锻炼肯定不多。
+
+因此,王争结合自己过去十多年工作中积累的项目经验,为每节课、每个知识点都设计了真实的代码实例。希望用8个月的时间,通过整个专栏200多个实战案例,手把手带你高强度、刻意地练习设计模式,潜移默化地提高你的设计编码能力,教会你如何编写高质量代码,帮你跨过知识到应用的鸿沟。
+
+# 一、专栏会讲哪些知识?
+
+整个专栏以23种设计模式为核心,从面向对象、设计原则、编程规范、代码重构铺开,带你追本溯源,一次性全面掌握编写高质量代码的所有知识。下面是专栏的知识概览图。
+
+![](https://static001.geekbang.org/resource/image/f3/d3/f3262ef8152517d3b11bfc3f2d2b12d3.png)
+
+# 二、专栏模块是怎么设置的?
+
+专栏共100期正文和10期不定期加餐,分为5个模块。
+
+学习导读部分,首先帮你明确设计模式知识的实际用途,帮你梳理最重要、最常用的7大代码评判标准,带你认识整个课程的知识框架,明确学习的任务,为后面的具体学习做好准备。
+
+设计原则与思想部分,将为你详细讲解面向对象、设计原则、编码规范、重构技巧等基础编码知识。每一个知识点分别通过“理论篇”来精讲,通过“实战篇”带你应用,通过“总结篇”带你复习巩固。
+
+设计模式与范式部分,将精讲23种经典设计模式,帮你搞懂每一种设计模式的原理、核心思想和应用场景,并告诉你如何避免过度设计和设计不足,一次性彻底掌握设计模式相关的所有知识。
+
+开源与项目实战部分,将带你剖析5个常用开源框架用到的设计模式、原则和思想,并通过完整的分析、设计和实现过程,手把手带你完成3个实战项目,将学过的理论知识应用到实战中。
+
+加餐部分,将随专栏进度不定期进行更新。这一部分将和你分享作者十多年工作中总结出的学习方法、工作心得和人生经验。
+
+  
+
+## 课程目录
+
+![](https://static001.geekbang.org/resource/image/d2/e4/d22ec8bc4e1c2ea85536537b36e366e4.jpg)
+
+  
+
+## 适合人群
+
+1.专栏中的代码是用 Java 语言实现的,但是专栏内容的讲解并不与具体的编程语言挂钩。只要你熟悉一门编程语言即可。
+
+2.专栏重点面向偏后端的程序员,有一定项目经验会更好,没有也完全没有关系。
+
+  
+
+## 特别放送
+
+1.  订阅后分享海报,每邀一位好友订阅有现金返现。
+2.  戳此[申请学生认证](https://promo.geekbang.org/activity/student-certificate?utm_source=app&utm_medium=xiangqingye),享五折优惠。
+3.  6 - 7月课表抢先看,充值购课更划算![充 ¥500 得 ¥600,](https://shop18793264.m.youzan.com/wscgoods/detail/3eo35rfkgprso?scan=1&activity=none&from=kdt&qr=directgoods_950523950&shopAutoEnter=1)好礼免费送!
+    
+4.  戳此[申请技术交流&福利群](https://jinshuju.net/f/OCQKLn )
+  
+
+#### 免费领取福利
+
+[![](https://static001.geekbang.org/resource/image/a1/4a/a1254c5523a33bf77ec32edfbc8e5f4a.jpg)](https://time.geekbang.org/article/354230)  
+  
+
+#### 限时活动推荐
+
+[![](https://static001.geekbang.org/resource/image/03/2e/03f8b2532b4261666f13d9bda85c0b2e.jpg)](https://shop18793264.m.youzan.com/wscgoods/detail/3eo35rfkgprso?scan=1&activity=none&from=kdt&qr=directgoods_950523950&shopAutoEnter=1)
+
+  
+
+## 订阅须知
+
+1.  本专栏为订阅专栏,形式为图文+音频,现已更新完毕。订阅成功后,即可通过“极客时间”App端、小程序端、Web端永久阅读。
+2.  企业采购推荐使用“[极客时间企业版](https://b.geekbang.org/?utm_source=geektime&utm_medium=columnintro&utm_campaign=newregister&gk_source=2021020901_gkcolumnintro_newregister)”,便捷安排员工学习计划,掌握团队学习仪表盘。
+3.  本专栏为虚拟商品,一经订阅,概不退款。

+ 116 - 0
README.md

@@ -0,0 +1,116 @@
+# 收集于互联网,下载后请于24小时内删除,侵权联系删除
+
+* [简介](./INTRODUCTION.md)
+* [开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!](./docs/160463.md)
+* [01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?](./docs/160981.md)
+* [02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?](./docs/160985.md)
+* [03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?](./docs/160991.md)
+* [04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?](./docs/161575.md)
+* [05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?](./docs/161114.md)
+* [06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?](./docs/161587.md)
+* [07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?](./docs/164907.md)
+* [08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?](./docs/165103.md)
+* [09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?](./docs/165114.md)
+* [10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?](./docs/169593.md)
+* [11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?](./docs/169600.md)
+* [12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?](./docs/169631.md)
+* [13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?](./docs/171760.md)
+* [14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?](./docs/171767.md)
+* [15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?](./docs/171771.md)
+* [16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?](./docs/176075.md)
+* [17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?](./docs/177110.md)
+* [18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?](./docs/177442.md)
+* [19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?](./docs/177444.md)
+* [20 | 理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?](./docs/177448.md)
+* [21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?](./docs/179607.md)
+* [22 | 理论八:如何用迪米特法则(LOD)实现“高内聚、松耦合”?](./docs/179615.md)
+* [23 | 实战一(上):针对业务系统的开发,如何做需求分析和设计?](./docs/182001.md)
+* [24 | 实战一(下):如何实现一个遵从设计原则的积分兑换系统?](./docs/183007.md)
+* [25 | 实战二(上):针对非业务的通用框架开发,如何做需求分析和设计?](./docs/179644.md)
+* [26 | 实战二(下):如何实现一个支持各种统计规则的性能计数器?](./docs/179673.md)
+* [27 | 理论一:什么情况下要重构?到底重构什么?又该如何重构?](./docs/179679.md)
+* [28 | 理论二:为了保证重构不出错,有哪些非常能落地的技术手段?](./docs/185684.md)
+* [29 | 理论三:什么是代码的可测试性?如何写出可测试性好的代码?](./docs/186691.md)
+* [30 | 理论四:如何通过封装、抽象、模块化、中间层等解耦代码?](./docs/187761.md)
+* [31 | 理论五:让你最快速地改善代码质量的20条编程规范(上)](./docs/188622.md)
+* [32 | 理论五:让你最快速地改善代码质量的20条编程规范(中)](./docs/188857.md)
+* [33 | 理论五:让你最快速地改善代码质量的20条编程规范(下)](./docs/188882.md)
+* [34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题](./docs/190979.md)
+* [35 | 实战一(下):手把手带你将ID生成器代码从“能用”重构为“好用”](./docs/191621.md)
+* [36 | 实战二(上):程序出错该返回啥?NULL、异常、错误码、空对象?](./docs/191642.md)
+* [37 | 实战二(下):重构ID生成器项目中各函数的异常处理代码](./docs/191647.md)
+* [38 | 总结回顾面向对象、设计原则、编程规范、重构技巧等知识点](./docs/193093.md)
+* [39 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(上)](./docs/193221.md)
+* [40 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(下)](./docs/193555.md)
+* [41 | 单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?](./docs/194035.md)
+* [42 | 单例模式(中):我为什么不推荐使用单例模式?又有何替代方案?](./docs/194068.md)
+* [43 | 单例模式(下):如何设计实现一个集群环境下的分布式单例模式?](./docs/196790.md)
+* [44 | 工厂模式(上):我为什么说没事不要随便用工厂模式创建对象?](./docs/197254.md)
+* [45 | 工厂模式(下):如何设计实现一个Dependency Injection框架?](./docs/198614.md)
+* [46 | 建造者模式:详解构造函数、set方法、建造者模式三种对象创建方式](./docs/199674.md)
+* [47 | 原型模式:如何最快速地clone一个HashMap散列表?](./docs/200786.md)
+* [48 | 代理模式:代理在RPC、缓存、监控等场景中的应用](./docs/201823.md)
+* [49 | 桥接模式:如何实现支持不同类型和渠道的消息推送系统?](./docs/202786.md)
+* [50 | 装饰器模式:通过剖析Java IO类库源码学习装饰器模式](./docs/204845.md)
+* [51 | 适配器模式:代理、适配器、桥接、装饰,这四个模式有何区别?](./docs/205912.md)
+* [52 | 门面模式:如何设计合理的接口粒度以兼顾接口的易用性和通用性?](./docs/206409.md)
+* [53 | 组合模式:如何设计实现支持递归遍历的文件系统目录树结构?](./docs/207456.md)
+* [54 | 享元模式(上):如何利用享元模式优化文本编辑器的内存占用?](./docs/208572.md)
+* [55 | 享元模式(下):剖析享元模式在Java Integer、String中的应用](./docs/209343.md)
+* [56 | 观察者模式(上):详解各种应用场景下观察者模式的不同实现方式](./docs/210170.md)
+* [57 | 观察者模式(下):如何实现一个异步非阻塞的EventBus框架?](./docs/211239.md)
+* [58 | 模板模式(上):剖析模板模式在JDK、Servlet、JUnit等中的应用](./docs/212049.md)
+* [59 | 模板模式(下):模板模式与Callback回调函数有何区别和联系?](./docs/212802.md)
+* [60 | 策略模式(上):如何避免冗长的if-else/switch分支判断代码?](./docs/214014.md)
+* [61 | 策略模式(下):如何实现一个支持给不同大小文件排序的小程序?](./docs/215132.md)
+* [62 | 职责链模式(上):如何实现可灵活扩展算法的敏感信息过滤框架?](./docs/216278.md)
+* [63 | 职责链模式(下):框架中常用的过滤器、拦截器是如何实现的?](./docs/217395.md)
+* [64 | 状态模式:游戏、工作流引擎中常用的状态机是如何实现的?](./docs/218375.md)
+* [65 | 迭代器模式(上):相比直接遍历集合数据,使用迭代器有哪些优势?](./docs/219290.md)
+* [66 | 迭代器模式(中):遍历集合的同时,为什么不能增删集合元素?](./docs/219964.md)
+* [67 | 迭代器模式(下):如何设计实现一个支持“快照”功能的iterator?](./docs/221269.md)
+* [68 | 访问者模式(上):手把手带你还原访问者模式诞生的思维过程](./docs/221852.md)
+* [69 | 访问者模式(下):为什么支持双分派的语言不需要访问者模式?](./docs/222762.md)
+* [70 | 备忘录模式:对于大对象的备份和恢复,如何优化内存和时间的消耗?](./docs/223947.md)
+* [71 | 命令模式:如何利用命令模式实现一个手游后端架构?](./docs/224549.md)
+* [72 | 解释器模式:如何设计实现一个自定义接口告警规则功能?](./docs/225904.md)
+* [73 | 中介模式:什么时候用中介模式?什么时候用观察者模式?](./docs/226710.md)
+* [74 | 总结回顾23种经典设计模式的原理、背后的思想、应用场景等](./docs/227452.md)
+* [75 | 在实际的项目开发中,如何避免过度设计?又如何避免设计不足?](./docs/229157.md)
+* [76 |  开源实战一(上):通过剖析Java JDK源码学习灵活应用设计模式](./docs/229996.md)
+* [77 | 开源实战一(下):通过剖析Java JDK源码学习灵活应用设计模式](./docs/230708.md)
+* [78 | 开源实战二(上):从Unix开源开发学习应对大型复杂项目开发](./docs/232061.md)
+* [79 | 开源实战二(中):从Unix开源开发学习应对大型复杂项目开发](./docs/232427.md)
+* [80 | 开源实战二(下):从Unix开源开发学习应对大型复杂项目开发](./docs/232687.md)
+* [81 | 开源实战三(上):借Google Guava学习发现和开发通用功能模块](./docs/233742.md)
+* [82 | 开源实战三(中):剖析Google Guava中用到的几种设计模式](./docs/234758.md)
+* [83 | 开源实战三(下):借Google Guava学习三大编程范式中的函数式编程](./docs/235334.md)
+* [84 | 开源实战四(上):剖析Spring框架中蕴含的经典设计思想或原则](./docs/236935.md)
+* [85 | 开源实战四(中):剖析Spring框架中用来支持扩展的两种设计模式](./docs/237810.md)
+* [86 | 开源实战四(下):总结Spring框架用到的11种设计模式](./docs/238418.md)
+* [87 | 开源实战五(上):MyBatis如何权衡易用性、性能和灵活性?](./docs/239239.md)
+* [88 | 开源实战五(中):如何利用职责链与代理模式实现MyBatis Plugin?](./docs/240147.md)
+* [89 | 开源实战五(下):总结MyBatis框架中用到的10种设计模式](./docs/240971.md)
+* [90 | 项目实战一:设计实现一个支持各种算法的限流框架(分析)](./docs/242314.md)
+* [91 | 项目实战一:设计实现一个支持各种算法的限流框架(设计)](./docs/243175.md)
+* [92 | 项目实战一:设计实现一个支持各种算法的限流框架(实现)](./docs/243961.md)
+* [93 | 项目实战二:设计实现一个通用的接口幂等框架(分析)](./docs/245022.md)
+* [94 | 项目实战二:设计实现一个通用的接口幂等框架(设计)](./docs/245788.md)
+* [95 | 项目实战二:设计实现一个通用的接口幂等框架(实现)](./docs/246379.md)
+* [96 | 项目实战三:设计实现一个支持自定义规则的灰度发布组件(分析)](./docs/247776.md)
+* [97  | 项目实战三:设计实现一个支持自定义规则的灰度发布组件(设计)](./docs/248714.md)
+* [98 | 项目实战三:设计实现一个支持自定义规则的灰度发布组件(实现)](./docs/249369.md)
+* [99 | 总结回顾:在实际软件开发中常用的设计思想、原则和模式](./docs/250942.md)
+* [100 | 如何将设计思想、原则、模式等理论知识应用到项目中?](./docs/251930.md)
+* [加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法](./docs/166698.md)
+* [加餐二 | 设计模式、重构、编程规范等相关书籍推荐](./docs/172690.md)
+* [春节特别加餐 | 王争:如何学习《设计模式之美》专栏?](./docs/192789.md)
+* [加餐三 | 聊一聊Google是如何做Code Review的](./docs/252937.md)
+* [加餐四 | 聊一聊Google那些让我快速成长的地方](./docs/254190.md)
+* [加餐五 | 听一听小争哥对Google工程师文化的解读](./docs/255037.md)
+* [加餐六 | 什么才是所谓的编程能力?如何考察一个人的编程能力?](./docs/255697.md)
+* [加餐七 | 基础学科的知识如何转化成实际的技术生产力?](./docs/256866.md)
+* [加餐八 | 程序员怎么才能让自己走得更高、更远?](./docs/257513.md)
+* [加餐九 | 作为面试官或候选人,如何面试或回答设计模式问题?](./docs/258207.md)
+* [加餐十 | 如何接手一坨烂业务代码?如何在烂业务代码中成长?](./docs/259489.md)
+* [结束语  | 聊一聊机遇、方向、能力、努力!](./docs/260184.md)

+ 4 - 0
book.json

@@ -0,0 +1,4 @@
+{
+  "title": "设计模式之美",
+  "language": "zh"
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 86 - 0
docs/160463.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 85 - 0
docs/160981.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 130 - 0
docs/160985.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 114 - 0
docs/160991.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 330 - 0
docs/161114.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 96 - 0
docs/161575.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 204 - 0
docs/161587.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 251 - 0
docs/164907.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 312 - 0
docs/165103.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 193 - 0
docs/165114.md


+ 431 - 0
docs/166698.md

@@ -0,0 +1,431 @@
+# 加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
+
+尽管说设计模式跟编程语言没有直接关系,但是,我们也无法完全脱离代码来讲设计模式。我本人熟悉的是Java语言,所以专栏中的代码示例我都是用Java语言来写的。考虑到有些同学并不熟悉Java语言,我今天用一篇文章介绍一下专栏中用到的Java语法。
+
+如果你有一定的编程基础,熟悉一门编程语言,结合我今天讲的Java语法知识,那看懂专栏中的代码基本不成问题。
+
+如果你熟悉的是C/C++、C#、PHP,那几乎不用费多大力气,就能看懂Java代码。我当时从C++转到Java,也只看了一天的书,基本语法就全部掌握了。
+
+如果你熟悉的是Python、Go、Ruby、JavaScript,这些语言的语法可能跟Java的区别稍微有些大,但是,通过这篇文章,做到能看懂也不是难事儿。
+
+好了,现在,就让我们一块儿看下,专栏中用到的所有Java语言的语法。
+
+## Hello World
+
+我们先来看一下,Java语言的Hello World代码如何编写。
+
+在Java中,所有的代码都必须写在类里面,所以,我们定义一个HelloWorld类。main()函数是程序执行的入口。main()函数中调用了Java开发包JDK提供的打印函数System.out.println()来打印hello world字符串。除此之外,Java中有两种代码注释方式,第一种是“//注释…”双斜杠,表示后面的字符串都是注释,第二种是“/\*注释…\*/”,表示中间的内容都是注释。
+
+```
+/*hello world程序*/
+public class HelloWorld {
+    public static void main(String []args) {
+        System.out.println("Hello World"); //打印Hello World
+    }
+}
+
+```
+
+## 基本数据类型
+
+Java语言中的基本数据类型跟其他语言类似,主要有下面几种:
+
+*   整型类型:byte(字节)、short(短整型)、int(整型)、long(长整型)
+*   浮点类型:float(单精度浮点)、double(双精度浮点)
+*   字符型:char
+*   布尔型:boolean
+
+如下,我们来定义一个基本类型变量:
+
+```
+int a = 6;
+
+```
+
+除此之外,为了方便我们使用,Java还提供了一些封装这些基本数据类型的类,这些类实现了一些常用的功能函数,可以直接拿来使用。常用的有下面几个类:
+
+*   Integer:对应封装了基本类型int;
+*   Long:对应封装了基本类型long;
+*   Float:对应封装了基本类型float;
+*   Double:对应封装了基本类型double;
+*   Boolean:对应封装了基本类型boolean;
+*   String:对应封装了字符串类型char\[\]。
+
+如下,我们来定义一个Integer对象:
+
+```
+Integer oa = new Integer(6);
+
+```
+
+## 数组
+
+Java中,我们使用\[\]来定义一个数组,如下所示:
+
+```
+int a[] = new int[10]; //定义了一个长度是10的int类型数组
+
+```
+
+在Java中,我们通过如下方式访问数组中的元素:
+
+```
+a[1] = 3; //将下标是1的数组元素赋值为3
+System.out.println(a[2]); //打印下标是2的数组元素值
+
+```
+
+## 流程控制
+
+流程控制语句跟其他语言类似,主要有下面几种。
+
+*   if-else语句,代码示例如下所示:
+
+```
+// 用法一
+int a;
+if (a > 1) {
+  //执行代码块
+} else {
+  //执行代码块
+}
+
+// 用法二
+int a;
+if (a > 1) {
+  //执行代码块
+} else if (a == 1) {
+  //执行代码块
+} else {
+  //执行代码块
+}
+
+```
+
+*   switch-case语句,代码示例如下所示:
+
+```
+int a;
+switch (a) {
+  case 1:
+    //执行代码块
+    break;
+  case 2:
+    //执行代码块
+    break;
+  default:
+    //默认执行代码
+}
+
+```
+
+*   for、while循环,代码示例如下所示:
+
+```
+for (int i = 0; i < 10; ++i) {
+  // 循环执行10次此代码块
+}
+
+int i = 0;
+while (i < 10) {
+  // 循环执行10次此代码块
+}
+
+```
+
+*   continue、break、return,代码示例如下所示:
+
+```
+for (int i = 0; i < 10; ++i) {
+  if (i == 4) {
+    continue; //跳过本次循环,不会打印出4这个值
+  }
+  System.out.println(i);
+}
+
+for (int i = 0; i < 10; ++i) {
+  if (i == 4) {
+    break; //提前终止循环,只会打印0、1、2、3
+  }
+  System.out.println(i);
+}
+
+public void func(int a) {
+  if (a == 1) {
+    return; //结束一个函数,从此处返回
+  }
+  System.out.println(a);
+}
+
+```
+
+## 类、对象
+
+Java语言使用关键词class来定义一个类,类中包含成员变量(也叫作属性)和方法(也叫作函数),其中有一种特殊的函数叫作构造函数,其命名比较固定,跟类名相同。除此之外,Java语言通过new关键词来创建一个类的对象,并且可以通过构造函数,初始化一些成员变量的值。代码示例如下所示:
+
+```
+public class Dog { // 定义了一个Dog类
+  private int age; // 属性或者成员变量
+  private int weight;
+
+  public Dog(int age, int weight) { // 构造函数
+    this.age = age;
+    this.weight = weight;
+  }
+
+  public int getAge() { // 函数或者方法
+    return age;
+  }
+  
+  public int getWeigt() {
+    return weight;
+  }
+  
+  public void run() {
+    // ...
+  }
+}
+
+Dog dog1 = new Dog(2, 10);//通过new关键词创建了一个Dog对象dog1
+int age = dog1.getAge();//调用dog1的getAge()方法
+dog1.run();//调用dog1的run()方法
+
+```
+
+## 权限修饰符
+
+在前面的代码示例中,我们多次用到private、public,它们跟protected一起,构成了Java语言的三个权限修饰符。权限修饰符可以修饰函数、成员变量。
+
+*   private修饰的函数或者成员变量,只能在类内部使用。
+*   protected修饰的函数或者成员变量,可以在类及其子类内使用。
+*   public修饰的函数或者成员变量,可以被任意访问。
+
+除此之外,权限修饰符还可以修饰类,不过,专栏中所有的类定义都是public访问权限的,所以,我们可以不用去了解三个修饰符修饰类的区别。
+
+对于权限修饰符的理解,我们可以参看下面的代码示例:
+
+```
+public class Dog {// public修饰类
+  private int age; // private修饰属性,只能在类内部使用
+  private int weight;
+  
+  public Dog(int age, int weight) {
+    this.age = age;
+    this.weight = weight;
+  }
+
+  public int getAge() { //public修饰的方法,任意代码都是可以调用
+    return age;
+  }
+  
+  public void run() {
+    // ...
+  }
+
+}
+
+```
+
+## 继承
+
+Java语言使用extends关键字来实现继承。被继承的类叫作父类,继承类叫作子类。子类继承父类的所有非private属性和方法。具体的代码示例如下所示:
+
+```
+public class Animal { // 父类
+  protected int age;
+  protected int weight;
+  
+  public Animal(int age, int weight) {
+    this.age = age;
+    this.weight = weight;
+  }
+  
+  public int getAge() { // 函数或者方法
+    return age;
+  }
+  
+  public int getWeigt() {
+    return weight;
+  }
+  
+  public void run() {
+    // ...
+  }
+}
+
+public class Dog extends Animal { // 子类
+  public Dog(int age, int weight) { // 构造函数
+    super(age, weight); //调用父类的构造函数
+  }
+
+  public void wangwang() {
+    //...
+  }
+}
+
+public class Cat extends Animal { //子类
+  public Cat(int age, int weight) { // 构造函数
+    super(age, weight); //调用父类的构造函数
+  }
+  
+  public void miaomiao() {
+    //...
+  }
+}
+
+//使用举例
+Dog dog = new Dog(2, 8);
+dog.run();
+dog.wangwang();
+Cat cat = new Cat(1, 3);
+cat.run();
+cat.miaomiao();
+
+```
+
+## 接口
+
+Java语言通过interface关键字来定义接口。接口中只能声明方法,不能包含实现,也不能定义属性。类通过implements关键字来实现接口中定义的方法。在专栏的第8讲中,我们会详细讲解接口,所以,这里我只简单介绍一下语法。具体的代码示例如下所示:
+
+```
+public interface Runnable {
+  void run();
+}
+
+public class Dog implements Runnable {
+  private int age; // 属性或者成员变量
+  private int weight;
+
+  public Dog(int age, int weight) { // 构造函数
+    this.age = age;
+    this.weight = weight;
+  }
+
+  public int getAge() { // 函数或者方法
+    return age;
+  }
+
+  public int getWeigt() {
+    return weight;
+  }
+
+  @Override 
+  public void run() { //实现接口中定义的run()方法
+    // ...
+  }
+}
+
+```
+
+## 容器
+
+Java提供了一些现成的容器。容器可以理解为一些工具类,底层封装了各种数据结构。比如ArrayList底层就是数组,LinkedList底层就是链表,HashMap底层就是散列表等。这些容器我们可以拿来直接使用,不用从零开始开发,大大提高了编码的效率。具体的代码示例如下所示:
+
+```
+public class DemoA {
+  private ArrayList<User> users;
+  
+  public void addUser(User user) {
+    users.add(user);
+  }
+}
+
+```
+
+## 异常处理
+
+Java提供了异常这种出错处理机制。我们可以指直接使用JDK提供的现成的异常类,也可以自定义异常。在Java中,我们通过关键字throw来抛出一个异常,通过throws声明函数抛出异常,通过try-catch-finally语句来捕获异常。代码示例如下所示:
+
+```
+public class UserNotFoundException extends Exception { // 自定义一个异常
+  public UserNotFoundException() {
+    super();
+  }
+
+  public UserNotFoundException(String message) {
+    super(message);
+  }
+
+  public UserNotFoundException(String message, Throwable e) {
+    super(message, e);
+  }
+}
+
+public class UserService {
+  private UserRepository userRepo;
+  public UserService(UseRepository userRepo) {
+    this.userRepo = userRepo;
+  }
+
+  public User getUserById(long userId) throws UserNotFoundException {
+    User user = userRepo.findUserById(userId);
+    if (user == null) { // throw用来抛出异常
+      throw new UserNotFoundException();//代码从此处返回
+    }
+    return user;
+  }
+}
+
+public class UserController {
+  private UserService userService;
+  public UserController(UserService userService) {
+    this.userService = userService;
+  }
+  
+  public User getUserById(long userId) {
+    User user = null;
+    try { //捕获异常
+      user = userService.getUserById(userId);
+    } catch (UserNotFoundException e) {
+      System.out.println("User not found: " + userId);
+    } finally { //不管异常会不会发生,finally包裹的语句块总会被执行
+      System.out.println("I am always printed.");
+    }
+    return user;
+  }
+}
+
+```
+
+## package包
+
+Java通过pacakge关键字来分门别类地组织类,通过import关键字来引入类或者package。具体的代码示例如下所示:
+
+```
+/*class DemoA*/
+package com.xzg.cd; // 包名com.xzg.cd
+
+public class DemoA {
+  //...
+}
+
+/*class DemoB*/
+package com.xzg.alg;
+
+import java.util.HashMap; // Java工具包JDK中的类
+import java.util.Map;
+import com.xzg.cd.DemoA;
+
+public class DemoB {
+  //...
+}
+
+```
+
+## 总结
+
+今天,我带你一块学习了专栏中用到的所有的Java基本语法。不过,我希望你不要纠结于专栏或者某某书籍到底是用什么编程语言来写的。语言层面的东西完全不会限制我的讲解和你的理解。这就像我们读小说一样,不管它是用英语写的,还是中文写的,故事都可以同样精彩。而且,多了解一些Java语法,对于你今后阅读Java语言编写的书籍或者文档,也很有帮助。
+
+实际上,我之前在Google工作的时候,大家都不太在意自己熟悉的是哪种编程语言,很多同事都是“现学现卖”,什么项目适合用什么语言就现学什么语言。除此之外,Google在招聘的时候,也不限定候选人一定要熟悉哪种编程语言,也很少问跟语言特性相关的问题。因为他们觉得,编程语言只是一个工具,对于一个有一定学习能力的人,学习一门编程语言并不是件难事。
+
+除此之外,对于专栏中的代码示例,你也可以用你熟悉语言重新实现一遍,我相信这也是件很有意义的事情,也更能加深你对内容的理解。
+
+## 课堂讨论
+
+不同的公司开发使用的编程语言可能不一样,比如阿里一般都是用Java,今日头条用Go、C++比较多。在招聘上,这些公司都倾向于招聘熟悉相应编程语言的同学,毕竟熟练掌握一门语言也是要花不少时间的,而且用熟悉的编程语言来开发,肯定会更得心应手,更不容易出错。今天课堂讨论的话题有两个:
+
+1.  分享一下你学习一门编程语言的经历,从入门到熟练掌握,大约花了多久的时间?有什么好的学习编程语言的方法?
+2.  在一个程序员的技术能力评价体系中,你觉得“熟练使用某种编程语言”所占的比重有多大?
+
+欢迎在留言区写下你的想法,和同学一起交流和分享。如果有收获,也欢迎你把这篇文章分享给你的朋友。
+    

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 194 - 0
docs/169593.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 152 - 0
docs/169600.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 343 - 0
docs/169631.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 101 - 0
docs/171760.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 277 - 0
docs/171767.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 182 - 0
docs/171771.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 76 - 0
docs/172690.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 342 - 0
docs/176075.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 154 - 0
docs/177110.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 400 - 0
docs/177442.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 243 - 0
docs/177444.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 199 - 0
docs/177448.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 338 - 0
docs/179607.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 300 - 0
docs/179615.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 233 - 0
docs/179644.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 349 - 0
docs/179673.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 96 - 0
docs/179679.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 107 - 0
docs/182001.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 145 - 0
docs/183007.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 335 - 0
docs/185684.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 476 - 0
docs/186691.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 111 - 0
docs/187761.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 164 - 0
docs/188622.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 109 - 0
docs/188857.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 434 - 0
docs/188882.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 146 - 0
docs/190979.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 396 - 0
docs/191621.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 364 - 0
docs/191642.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 279 - 0
docs/191647.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 76 - 0
docs/192789.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 335 - 0
docs/193093.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 442 - 0
docs/193221.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 476 - 0
docs/193555.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 365 - 0
docs/194035.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 273 - 0
docs/194068.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 206 - 0
docs/196790.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 392 - 0
docs/197254.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 320 - 0
docs/198614.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 279 - 0
docs/199674.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 313 - 0
docs/200786.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 263 - 0
docs/201823.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 278 - 0
docs/202786.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 254 - 0
docs/204845.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 417 - 0
docs/205912.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 79 - 0
docs/206409.md


+ 342 - 0
docs/207456.md

@@ -0,0 +1,342 @@
+# 53 | 组合模式:如何设计实现支持递归遍历的文件系统目录树结构?
+
+结构型设计模式就快要讲完了,还剩下两个不那么常用的:组合模式和享元模式。今天,我们来讲一下**组合模式**(Composite Design Pattern)。
+
+组合模式跟我们之前讲的面向对象设计中的“组合关系(通过组合来组装两个类)”,完全是两码事。这里讲的“组合模式”,主要是用来处理树形结构数据。这里的“数据”,你可以简单理解为一组对象集合,待会我们会详细讲解。
+
+正因为其应用场景的特殊性,数据必须能表示成树形结构,这也导致了这种模式在实际的项目开发中并不那么常用。但是,一旦数据满足树形结构,应用这种模式就能发挥很大的作用,能让代码变得非常简洁。
+
+话不多说,让我们正式开始今天的学习吧!
+
+## 组合模式的原理与实现
+
+在GoF的《设计模式》一书中,组合模式是这样定义的:
+
+> Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly.
+
+翻译成中文就是:将一组对象组织(Compose)成树形结构,以表示一种“部分-整体”的层次结构。组合让客户端(在很多设计模式书籍中,“客户端”代指代码的使用者。)可以统一单个对象和组合对象的处理逻辑。
+
+接下来,对于组合模式,我举个例子来给你解释一下。
+
+假设我们有这样一个需求:设计一个类来表示文件系统中的目录,能方便地实现下面这些功能:
+
+*   动态地添加、删除某个目录下的子目录或文件;
+*   统计指定目录下的文件个数;
+*   统计指定目录下的文件总大小。
+
+我这里给出了这个类的骨架代码,如下所示。其中的核心逻辑并未实现,你可以试着自己去补充完整,再来看我的讲解。在下面的代码实现中,我们把文件和目录统一用FileSystemNode类来表示,并且通过isFile属性来区分。
+
+```
+public class FileSystemNode {
+  private String path;
+  private boolean isFile;
+  private List<FileSystemNode> subNodes = new ArrayList<>();
+
+  public FileSystemNode(String path, boolean isFile) {
+    this.path = path;
+    this.isFile = isFile;
+  }
+
+  public int countNumOfFiles() {
+    // TODO:...
+  }
+
+  public long countSizeOfFiles() {
+    // TODO:...
+  }
+
+  public String getPath() {
+    return path;
+  }
+
+  public void addSubNode(FileSystemNode fileOrDir) {
+    subNodes.add(fileOrDir);
+  }
+
+  public void removeSubNode(FileSystemNode fileOrDir) {
+    int size = subNodes.size();
+    int i = 0;
+    for (; i < size; ++i) {
+      if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
+        break;
+      }
+    }
+    if (i < size) {
+      subNodes.remove(i);
+    }
+  }
+}
+
+```
+
+实际上,如果你看过我的《数据结构与算法之美》专栏,想要补全其中的countNumOfFiles()和countSizeOfFiles()这两个函数,并不是件难事,实际上这就是树上的递归遍历算法。对于文件,我们直接返回文件的个数(返回1)或大小。对于目录,我们遍历目录中每个子目录或者文件,递归计算它们的个数或大小,然后求和,就是这个目录下的文件个数和文件大小。
+
+我把两个函数的代码实现贴在下面了,你可以对照着看一下。
+
+```
+  public int countNumOfFiles() {
+    if (isFile) {
+      return 1;
+    }
+    int numOfFiles = 0;
+    for (FileSystemNode fileOrDir : subNodes) {
+      numOfFiles += fileOrDir.countNumOfFiles();
+    }
+    return numOfFiles;
+  }
+
+  public long countSizeOfFiles() {
+    if (isFile) {
+      File file = new File(path);
+      if (!file.exists()) return 0;
+      return file.length();
+    }
+    long sizeofFiles = 0;
+    for (FileSystemNode fileOrDir : subNodes) {
+      sizeofFiles += fileOrDir.countSizeOfFiles();
+    }
+    return sizeofFiles;
+  }
+
+```
+
+单纯从功能实现角度来说,上面的代码没有问题,已经实现了我们想要的功能。但是,如果我们开发的是一个大型系统,从扩展性(文件或目录可能会对应不同的操作)、业务建模(文件和目录从业务上是两个概念)、代码的可读性(文件和目录区分对待更加符合人们对业务的认知)的角度来说,我们最好对文件和目录进行区分设计,定义为File和Directory两个类。
+
+按照这个设计思路,我们对代码进行重构。重构之后的代码如下所示:
+
+```
+public abstract class FileSystemNode {
+  protected String path;
+
+  public FileSystemNode(String path) {
+    this.path = path;
+  }
+
+  public abstract int countNumOfFiles();
+  public abstract long countSizeOfFiles();
+
+  public String getPath() {
+    return path;
+  }
+}
+
+public class File extends FileSystemNode {
+  public File(String path) {
+    super(path);
+  }
+
+  @Override
+  public int countNumOfFiles() {
+    return 1;
+  }
+
+  @Override
+  public long countSizeOfFiles() {
+    java.io.File file = new java.io.File(path);
+    if (!file.exists()) return 0;
+    return file.length();
+  }
+}
+
+public class Directory extends FileSystemNode {
+  private List<FileSystemNode> subNodes = new ArrayList<>();
+
+  public Directory(String path) {
+    super(path);
+  }
+
+  @Override
+  public int countNumOfFiles() {
+    int numOfFiles = 0;
+    for (FileSystemNode fileOrDir : subNodes) {
+      numOfFiles += fileOrDir.countNumOfFiles();
+    }
+    return numOfFiles;
+  }
+
+  @Override
+  public long countSizeOfFiles() {
+    long sizeofFiles = 0;
+    for (FileSystemNode fileOrDir : subNodes) {
+      sizeofFiles += fileOrDir.countSizeOfFiles();
+    }
+    return sizeofFiles;
+  }
+
+  public void addSubNode(FileSystemNode fileOrDir) {
+    subNodes.add(fileOrDir);
+  }
+
+  public void removeSubNode(FileSystemNode fileOrDir) {
+    int size = subNodes.size();
+    int i = 0;
+    for (; i < size; ++i) {
+      if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
+        break;
+      }
+    }
+    if (i < size) {
+      subNodes.remove(i);
+    }
+  }
+}
+
+```
+
+文件和目录类都设计好了,我们来看,如何用它们来表示一个文件系统中的目录树结构。具体的代码示例如下所示:
+
+```
+public class Demo {
+  public static void main(String[] args) {
+    /**
+     * /
+     * /wz/
+     * /wz/a.txt
+     * /wz/b.txt
+     * /wz/movies/
+     * /wz/movies/c.avi
+     * /xzg/
+     * /xzg/docs/
+     * /xzg/docs/d.txt
+     */
+    Directory fileSystemTree = new Directory("/");
+    Directory node_wz = new Directory("/wz/");
+    Directory node_xzg = new Directory("/xzg/");
+    fileSystemTree.addSubNode(node_wz);
+    fileSystemTree.addSubNode(node_xzg);
+
+    File node_wz_a = new File("/wz/a.txt");
+    File node_wz_b = new File("/wz/b.txt");
+    Directory node_wz_movies = new Directory("/wz/movies/");
+    node_wz.addSubNode(node_wz_a);
+    node_wz.addSubNode(node_wz_b);
+    node_wz.addSubNode(node_wz_movies);
+
+    File node_wz_movies_c = new File("/wz/movies/c.avi");
+    node_wz_movies.addSubNode(node_wz_movies_c);
+
+    Directory node_xzg_docs = new Directory("/xzg/docs/");
+    node_xzg.addSubNode(node_xzg_docs);
+
+    File node_xzg_docs_d = new File("/xzg/docs/d.txt");
+    node_xzg_docs.addSubNode(node_xzg_docs_d);
+
+    System.out.println("/ files num:" + fileSystemTree.countNumOfFiles());
+    System.out.println("/wz/ files num:" + node_wz.countNumOfFiles());
+  }
+}
+
+```
+
+我们对照着这个例子,再重新看一下组合模式的定义:“将一组对象(文件和目录)组织成树形结构,以表示一种‘部分-整体’的层次结构(目录与子目录的嵌套结构)。组合模式让客户端可以统一单个对象(文件)和组合对象(目录)的处理逻辑(递归遍历)。”
+
+实际上,刚才讲的这种组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。
+
+## 组合模式的应用场景举例
+
+刚刚我们讲了文件系统的例子,对于组合模式,我这里再举一个例子。搞懂了这两个例子,你基本上就算掌握了组合模式。在实际的项目中,遇到类似的可以表示成树形结构的业务场景,你只要“照葫芦画瓢”去设计就可以了。
+
+假设我们在开发一个OA系统(办公自动化系统)。公司的组织结构包含部门和员工两种数据类型。其中,部门又可以包含子部门和员工。在数据库中的表结构如下所示:
+
+![](https://static001.geekbang.org/resource/image/5b/8b/5b19dc0c296f728328794eab1f16a38b.jpg)
+
+我们希望在内存中构建整个公司的人员架构图(部门、子部门、员工的隶属关系),并且提供接口计算出部门的薪资成本(隶属于这个部门的所有员工的薪资和)。
+
+部门包含子部门和员工,这是一种嵌套结构,可以表示成树这种数据结构。计算每个部门的薪资开支这样一个需求,也可以通过在树上的遍历算法来实现。所以,从这个角度来看,这个应用场景可以使用组合模式来设计和实现。
+
+这个例子的代码结构跟上一个例子的很相似,代码实现我直接贴在了下面,你可以对比着看一下。其中,HumanResource是部门类(Department)和员工类(Employee)抽象出来的父类,为的是能统一薪资的处理逻辑。Demo中的代码负责从数据库中读取数据并在内存中构建组织架构图。
+
+```
+public abstract class HumanResource {
+  protected long id;
+  protected double salary;
+
+  public HumanResource(long id) {
+    this.id = id;
+  }
+
+  public long getId() {
+    return id;
+  }
+
+  public abstract double calculateSalary();
+}
+
+public class Employee extends HumanResource {
+  public Employee(long id, double salary) {
+    super(id);
+    this.salary = salary;
+  }
+
+  @Override
+  public double calculateSalary() {
+    return salary;
+  }
+}
+
+public class Department extends HumanResource {
+  private List<HumanResource> subNodes = new ArrayList<>();
+
+  public Department(long id) {
+    super(id);
+  }
+
+  @Override
+  public double calculateSalary() {
+    double totalSalary = 0;
+    for (HumanResource hr : subNodes) {
+      totalSalary += hr.calculateSalary();
+    }
+    this.salary = totalSalary;
+    return totalSalary;
+  }
+
+  public void addSubNode(HumanResource hr) {
+    subNodes.add(hr);
+  }
+}
+
+// 构建组织架构的代码
+public class Demo {
+  private static final long ORGANIZATION_ROOT_ID = 1001;
+  private DepartmentRepo departmentRepo; // 依赖注入
+  private EmployeeRepo employeeRepo; // 依赖注入
+
+  public void buildOrganization() {
+    Department rootDepartment = new Department(ORGANIZATION_ROOT_ID);
+    buildOrganization(rootDepartment);
+  }
+
+  private void buildOrganization(Department department) {
+    List<Long> subDepartmentIds = departmentRepo.getSubDepartmentIds(department.getId());
+    for (Long subDepartmentId : subDepartmentIds) {
+      Department subDepartment = new Department(subDepartmentId);
+      department.addSubNode(subDepartment);
+      buildOrganization(subDepartment);
+    }
+    List<Long> employeeIds = employeeRepo.getDepartmentEmployeeIds(department.getId());
+    for (Long employeeId : employeeIds) {
+      double salary = employeeRepo.getEmployeeSalary(employeeId);
+      department.addSubNode(new Employee(employeeId, salary));
+    }
+  }
+}
+
+```
+
+我们再拿组合模式的定义跟这个例子对照一下:“将一组对象(员工和部门)组织成树形结构,以表示一种‘部分-整体’的层次结构(部门与子部门的嵌套结构)。组合模式让客户端可以统一单个对象(员工)和组合对象(部门)的处理逻辑(递归遍历)。”
+
+## 重点回顾
+
+好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
+
+组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。
+
+组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现。使用组合模式的前提在于,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景也比较局限,它并不是一种很常用的设计模式。
+
+## 课堂讨论
+
+在文件系统那个例子中,countNumOfFiles()和countSizeOfFiles()这两个函数实现的效率并不高,因为每次调用它们的时候,都要重新遍历一遍子树。有没有什么办法可以提高这两个函数的执行效率呢(注意:文件系统还会涉及频繁的删除、添加文件操作,也就是对应Directory类中的addSubNode()和removeSubNode()函数)?
+
+欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。
+    

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 285 - 0
docs/208572.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 231 - 0
docs/209343.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 196 - 0
docs/210170.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 392 - 0
docs/211239.md


+ 335 - 0
docs/212049.md

@@ -0,0 +1,335 @@
+# 58 | 模板模式(上):剖析模板模式在JDK、Servlet、JUnit等中的应用
+
+上两节课我们学习了第一个行为型设计模式,观察者模式。针对不同的应用场景,我们讲解了不同的实现方式,有同步阻塞、异步非阻塞的实现方式,也有进程内、进程间的实现方式。除此之外,我还带你手把手实现了一个简单的EventBus框架。
+
+今天,我们再学习另外一种行为型设计模式,模板模式。我们多次强调,绝大部分设计模式的原理和实现,都非常简单,难的是掌握应用场景,搞清楚能解决什么问题。模板模式也不例外。模板模式主要是用来解决复用和扩展两个问题。我们今天会结合Java Servlet、JUnit TestCase、Java InputStream、Java AbstractList四个例子来具体讲解这两个作用。
+
+话不多说,让我们正式开始今天的学习吧!
+
+## 模板模式的原理与实现
+
+模板模式,全称是模板方法设计模式,英文是Template Method Design Pattern。在GoF的《设计模式》一书中,它是这么定义的:
+
+> Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
+
+翻译成中文就是:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
+
+这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
+
+原理很简单,代码实现就更加简单,我写了一个示例代码,如下所示。templateMethod()函数定义为final,是为了避免子类重写它。method1()和method2()定义为abstract,是为了强迫子类去实现。不过,这些都不是必须的,在实际的项目开发中,模板模式的代码实现比较灵活,待会儿讲到应用场景的时候,我们会有具体的体现。
+
+```
+public abstract class AbstractClass {
+  public final void templateMethod() {
+    //...
+    method1();
+    //...
+    method2();
+    //...
+  }
+  
+  protected abstract void method1();
+  protected abstract void method2();
+}
+
+public class ConcreteClass1 extends AbstractClass {
+  @Override
+  protected void method1() {
+    //...
+  }
+  
+  @Override
+  protected void method2() {
+    //...
+  }
+}
+
+public class ConcreteClass2 extends AbstractClass {
+  @Override
+  protected void method1() {
+    //...
+  }
+  
+  @Override
+  protected void method2() {
+    //...
+  }
+}
+
+AbstractClass demo = ConcreteClass1();
+demo.templateMethod();
+
+```
+
+## 模板模式作用一:复用
+
+开篇的时候,我们讲到模板模式有两大作用:复用和扩展。我们先来看它的第一个作用:复用。
+
+模板模式把一个算法中不变的流程抽象到父类的模板方法templateMethod()中,将可变的部分method1()、method2()留给子类ContreteClass1和ContreteClass2来实现。所有的子类都可以复用父类中模板方法定义的流程代码。我们通过两个小例子来更直观地体会一下。
+
+### 1.Java InputStream
+
+Java IO类库中,有很多类的设计用到了模板模式,比如InputStream、OutputStream、Reader、Writer。我们拿InputStream来举例说明一下。
+
+我把InputStream部分相关代码贴在了下面。在代码中,read()函数是一个模板方法,定义了读取数据的整个流程,并且暴露了一个可以由子类来定制的抽象方法。不过这个方法也被命名为了read(),只是参数跟模板方法不同。
+
+```
+public abstract class InputStream implements Closeable {
+  //...省略其他代码...
+  
+  public int read(byte b[], int off, int len) throws IOException {
+    if (b == null) {
+      throw new NullPointerException();
+    } else if (off < 0 || len < 0 || len > b.length - off) {
+      throw new IndexOutOfBoundsException();
+    } else if (len == 0) {
+      return 0;
+    }
+
+    int c = read();
+    if (c == -1) {
+      return -1;
+    }
+    b[off] = (byte)c;
+
+    int i = 1;
+    try {
+      for (; i < len ; i++) {
+        c = read();
+        if (c == -1) {
+          break;
+        }
+        b[off + i] = (byte)c;
+      }
+    } catch (IOException ee) {
+    }
+    return i;
+  }
+  
+  public abstract int read() throws IOException;
+}
+
+public class ByteArrayInputStream extends InputStream {
+  //...省略其他代码...
+  
+  @Override
+  public synchronized int read() {
+    return (pos < count) ? (buf[pos++] & 0xff) : -1;
+  }
+}
+
+```
+
+### 2.Java AbstractList
+
+在Java AbstractList类中,addAll()函数可以看作模板方法,add()是子类需要重写的方法,尽管没有声明为abstract的,但函数实现直接抛出了UnsupportedOperationException异常。前提是,如果子类不重写是不能使用的。
+
+```
+public boolean addAll(int index, Collection<? extends E> c) {
+    rangeCheckForAdd(index);
+    boolean modified = false;
+    for (E e : c) {
+        add(index++, e);
+        modified = true;
+    }
+    return modified;
+}
+
+public void add(int index, E element) {
+    throw new UnsupportedOperationException();
+}
+
+```
+
+## 模板模式作用二:扩展
+
+模板模式的第二大作用的是扩展。这里所说的扩展,并不是指代码的扩展性,而是指框架的扩展性,有点类似我们之前讲到的控制反转,你可以结合[第19节](https://time.geekbang.org/column/article/177444)来一块理解。基于这个作用,模板模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。我们通过Junit TestCase、Java Servlet两个例子来解释一下。
+
+### 1.Java Servlet
+
+对于Java Web项目开发来说,常用的开发框架是SpringMVC。利用它,我们只需要关注业务代码的编写,底层的原理几乎不会涉及。但是,如果我们抛开这些高级框架来开发Web项目,必然会用到Servlet。实际上,使用比较底层的Servlet来开发Web项目也不难。我们只需要定义一个继承HttpServlet的类,并且重写其中的doGet()或doPost()方法,来分别处理get和post请求。具体的代码示例如下所示:
+
+```
+public class HelloServlet extends HttpServlet {
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    this.doPost(req, resp);
+  }
+  
+  @Override
+  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+    resp.getWriter().write("Hello World.");
+  }
+}
+
+```
+
+除此之外,我们还需要在配置文件web.xml中做如下配置。Tomcat、Jetty等Servlet容器在启动的时候,会自动加载这个配置文件中的URL和Servlet之间的映射关系。
+
+```
+<servlet>
+    <servlet-name>HelloServlet</servlet-name>
+    <servlet-class>com.xzg.cd.HelloServlet</servlet-class>
+</servlet>
+
+<servlet-mapping>
+    <servlet-name>HelloServlet</servlet-name>
+    <url-pattern>/hello</url-pattern>
+</servlet-mapping>
+
+```
+
+当我们在浏览器中输入网址(比如,[http://127.0.0.1:8080/hello](http://127.0.0.1:8080/hello) )的时候,Servlet容器会接收到相应的请求,并且根据URL和Servlet之间的映射关系,找到相应的Servlet(HelloServlet),然后执行它的service()方法。service()方法定义在父类HttpServlet中,它会调用doGet()或doPost()方法,然后输出数据(“Hello world”)到网页。
+
+我们现在来看,HttpServlet的service()函数长什么样子。
+
+```
+public void service(ServletRequest req, ServletResponse res)
+    throws ServletException, IOException
+{
+    HttpServletRequest  request;
+    HttpServletResponse response;
+    if (!(req instanceof HttpServletRequest &&
+            res instanceof HttpServletResponse)) {
+        throw new ServletException("non-HTTP request or response");
+    }
+    request = (HttpServletRequest) req;
+    response = (HttpServletResponse) res;
+    service(request, response);
+}
+
+protected void service(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException
+{
+    String method = req.getMethod();
+    if (method.equals(METHOD_GET)) {
+        long lastModified = getLastModified(req);
+        if (lastModified == -1) {
+            // servlet doesn't support if-modified-since, no reason
+            // to go through further expensive logic
+            doGet(req, resp);
+        } else {
+            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
+            if (ifModifiedSince < lastModified) {
+                // If the servlet mod time is later, call doGet()
+                // Round down to the nearest second for a proper compare
+                // A ifModifiedSince of -1 will always be less
+                maybeSetLastModified(resp, lastModified);
+                doGet(req, resp);
+            } else {
+                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            }
+        }
+    } else if (method.equals(METHOD_HEAD)) {
+        long lastModified = getLastModified(req);
+        maybeSetLastModified(resp, lastModified);
+        doHead(req, resp);
+    } else if (method.equals(METHOD_POST)) {
+        doPost(req, resp);
+    } else if (method.equals(METHOD_PUT)) {
+        doPut(req, resp);
+    } else if (method.equals(METHOD_DELETE)) {
+        doDelete(req, resp);
+    } else if (method.equals(METHOD_OPTIONS)) {
+        doOptions(req,resp);
+    } else if (method.equals(METHOD_TRACE)) {
+        doTrace(req,resp);
+    } else {
+        String errMsg = lStrings.getString("http.method_not_implemented");
+        Object[] errArgs = new Object[1];
+        errArgs[0] = method;
+        errMsg = MessageFormat.format(errMsg, errArgs);
+        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
+    }
+}
+
+```
+
+从上面的代码中我们可以看出,HttpServlet的service()方法就是一个模板方法,它实现了整个HTTP请求的执行流程,doGet()、doPost()是模板中可以由子类来定制的部分。实际上,这就相当于Servlet框架提供了一个扩展点(doGet()、doPost()方法),让框架用户在不用修改Servlet框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。
+
+### 2.JUnit TestCase
+
+跟Java Servlet类似,JUnit框架也通过模板模式提供了一些功能扩展点(setUp()、tearDown()等),让框架用户可以在这些扩展点上扩展功能。
+
+在使用JUnit测试框架来编写单元测试的时候,我们编写的测试类都要继承框架提供的TestCase类。在TestCase类中,runBare()函数是模板方法,它定义了执行测试用例的整体流程:先执行setUp()做些准备工作,然后执行runTest()运行真正的测试代码,最后执行tearDown()做扫尾工作。
+
+TestCase类的具体代码如下所示。尽管setUp()、tearDown()并不是抽象函数,还提供了默认的实现,不强制子类去重新实现,但这部分也是可以在子类中定制的,所以也符合模板模式的定义。
+
+```
+public abstract class TestCase extends Assert implements Test {
+  public void runBare() throws Throwable {
+    Throwable exception = null;
+    setUp();
+    try {
+      runTest();
+    } catch (Throwable running) {
+      exception = running;
+    } finally {
+      try {
+        tearDown();
+      } catch (Throwable tearingDown) {
+        if (exception == null) exception = tearingDown;
+      }
+    }
+    if (exception != null) throw exception;
+  }
+  
+  /**
+  * Sets up the fixture, for example, open a network connection.
+  * This method is called before a test is executed.
+  */
+  protected void setUp() throws Exception {
+  }
+
+  /**
+  * Tears down the fixture, for example, close a network connection.
+  * This method is called after a test is executed.
+  */
+  protected void tearDown() throws Exception {
+  }
+}
+
+```
+
+## 重点回顾
+
+好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
+
+模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
+
+在模板模式经典的实现中,模板方法定义为final,可以避免被子类重写。需要子类重写的方法定义为abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。
+
+模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
+
+## 课堂讨论
+
+假设一个框架中的某个类暴露了两个模板方法,并且定义了一堆供模板方法调用的抽象方法,代码示例如下所示。在项目开发中,即便我们只用到这个类的其中一个模板方法,我们还是要在子类中把所有的抽象方法都实现一遍,这相当于无效劳动,有没有其他方式来解决这个问题呢?
+
+```
+public abstract class AbstractClass {
+  public final void templateMethod1() {
+    //...
+    method1();
+    //...
+    method2();
+    //...
+  }
+  
+  public final void templateMethod2() {
+    //...
+    method3();
+    //...
+    method4();
+    //...
+  }
+  
+  protected abstract void method1();
+  protected abstract void method2();
+  protected abstract void method3();
+  protected abstract void method4();
+}
+
+```
+
+欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。
+    

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 350 - 0
docs/212802.md


+ 246 - 0
docs/214014.md

@@ -0,0 +1,246 @@
+# 60 | 策略模式(上):如何避免冗长的if-else/switch分支判断代码?
+
+上两节课中,我们学习了模板模式。模板模式主要起到代码复用和扩展的作用。除此之外,我们还讲到了回调,它跟模板模式的作用类似,但使用起来更加灵活。它们之间的主要区别在于代码实现,模板模式基于继承来实现,回调基于组合来实现。
+
+今天,我们开始学习另外一种行为型模式,策略模式。在实际的项目开发中,这个模式也比较常用。最常见的应用场景是,利用它来避免冗长的if-else或switch分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。
+
+对于策略模式,我们分两节课来讲解。今天,我们讲解策略模式的原理和实现,以及如何用它来避免分支判断逻辑。下一节课,我会通过一个具体的例子,来详细讲解策略模式的应用场景以及真正的设计意图。
+
+话不多说,让我们正式开始今天的学习吧!
+
+## 策略模式的原理与实现
+
+策略模式,英文全称是Strategy Design Pattern。在GoF的《设计模式》一书中,它是这样定义的:
+
+> Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
+
+翻译成中文就是:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
+
+我们知道,工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式跟两者类似,也能起到解耦的作用,不过,它解耦的是策略的定义、创建、使用这三部分。接下来,我就详细讲讲一个完整的策略模式应该包含的这三个部分。
+
+### 1.策略的定义
+
+策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。因为所有的策略类都实现相同的接口,所以,客户端代码基于接口而非实现编程,可以灵活地替换不同的策略。示例代码如下所示:
+
+```
+public interface Strategy {
+  void algorithmInterface();
+}
+
+public class ConcreteStrategyA implements Strategy {
+  @Override
+  public void  algorithmInterface() {
+    //具体的算法...
+  }
+}
+
+public class ConcreteStrategyB implements Strategy {
+  @Override
+  public void  algorithmInterface() {
+    //具体的算法...
+  }
+}
+
+```
+
+### 2.策略的创建
+
+因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。我们可以把根据type创建策略的逻辑抽离出来,放到工厂类中。示例代码如下所示:
+
+```
+public class StrategyFactory {
+  private static final Map<String, Strategy> strategies = new HashMap<>();
+
+  static {
+    strategies.put("A", new ConcreteStrategyA());
+    strategies.put("B", new ConcreteStrategyB());
+  }
+
+  public static Strategy getStrategy(String type) {
+    if (type == null || type.isEmpty()) {
+      throw new IllegalArgumentException("type should not be empty.");
+    }
+    return strategies.get(type);
+  }
+}
+
+```
+
+一般来讲,如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用getStrategy()的时候,都创建一个新的策略对象。针对这种情况,我们可以使用上面这种工厂类的实现方式,事先创建好每个策略对象,缓存到工厂类中,用的时候直接返回。
+
+相反,如果策略类是有状态的,根据业务场景的需要,我们希望每次从工厂方法中,获得的都是新创建的策略对象,而不是缓存好可共享的策略对象,那我们就需要按照如下方式来实现策略工厂类。
+
+```
+public class StrategyFactory {
+  public static Strategy getStrategy(String type) {
+    if (type == null || type.isEmpty()) {
+      throw new IllegalArgumentException("type should not be empty.");
+    }
+
+    if (type.equals("A")) {
+      return new ConcreteStrategyA();
+    } else if (type.equals("B")) {
+      return new ConcreteStrategyB();
+    }
+
+    return null;
+  }
+}
+
+```
+
+### 3.策略的使用
+
+刚刚讲了策略的定义和创建,现在,我们再来看一下,策略的使用。
+
+我们知道,策略模式包含一组可选策略,客户端代码一般如何确定使用哪个策略呢?最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。
+
+这里的“运行时动态”指的是,我们事先并不知道会使用哪个策略,而是在程序运行期间,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略。接下来,我们通过一个例子来解释一下。
+
+```
+// 策略接口:EvictionStrategy
+// 策略类:LruEvictionStrategy、FifoEvictionStrategy、LfuEvictionStrategy...
+// 策略工厂:EvictionStrategyFactory
+
+public class UserCache {
+  private Map<String, User> cacheData = new HashMap<>();
+  private EvictionStrategy eviction;
+
+  public UserCache(EvictionStrategy eviction) {
+    this.eviction = eviction;
+  }
+
+  //...
+}
+
+// 运行时动态确定,根据配置文件的配置决定使用哪种策略
+public class Application {
+  public static void main(String[] args) throws Exception {
+    EvictionStrategy evictionStrategy = null;
+    Properties props = new Properties();
+    props.load(new FileInputStream("./config.properties"));
+    String type = props.getProperty("eviction_type");
+    evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
+    UserCache userCache = new UserCache(evictionStrategy);
+    //...
+  }
+}
+
+// 非运行时动态确定,在代码中指定使用哪种策略
+public class Application {
+  public static void main(String[] args) {
+    //...
+    EvictionStrategy evictionStrategy = new LruEvictionStrategy();
+    UserCache userCache = new UserCache(evictionStrategy);
+    //...
+  }
+}
+
+```
+
+从上面的代码中,我们也可以看出,“非运行时动态确定”,也就是第二个Application中的使用方式,并不能发挥策略模式的优势。在这种应用场景下,策略模式实际上退化成了“面向对象的多态特性”或“基于接口而非实现编程原则”。
+
+## 如何利用策略模式避免分支判断?
+
+实际上,能够移除分支判断逻辑的模式不仅仅有策略模式,后面我们要讲的状态模式也可以。对于使用哪种模式,具体还要看应用场景来定。 策略模式适用于根据不同类型的动态,决定使用哪种策略这样一种应用场景。
+
+我们先通过一个例子来看下,if-else或switch-case分支判断逻辑是如何产生的。具体的代码如下所示。在这个例子中,我们没有使用策略模式,而是将策略的定义、创建、使用直接耦合在一起。
+
+```
+public class OrderService {
+  public double discount(Order order) {
+    double discount = 0.0;
+    OrderType type = order.getType();
+    if (type.equals(OrderType.NORMAL)) { // 普通订单
+      //...省略折扣计算算法代码
+    } else if (type.equals(OrderType.GROUPON)) { // 团购订单
+      //...省略折扣计算算法代码
+    } else if (type.equals(OrderType.PROMOTION)) { // 促销订单
+      //...省略折扣计算算法代码
+    }
+    return discount;
+  }
+}
+
+```
+
+如何来移除掉分支判断逻辑呢?那策略模式就派上用场了。我们使用策略模式对上面的代码重构,将不同类型订单的打折策略设计成策略类,并由工厂类来负责创建策略对象。具体的代码如下所示:
+
+```
+// 策略的定义
+public interface DiscountStrategy {
+  double calDiscount(Order order);
+}
+// 省略NormalDiscountStrategy、GrouponDiscountStrategy、PromotionDiscountStrategy类代码...
+
+// 策略的创建
+public class DiscountStrategyFactory {
+  private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();
+
+  static {
+    strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());
+    strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());
+    strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());
+  }
+
+  public static DiscountStrategy getDiscountStrategy(OrderType type) {
+    return strategies.get(type);
+  }
+}
+
+// 策略的使用
+public class OrderService {
+  public double discount(Order order) {
+    OrderType type = order.getType();
+    DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
+    return discountStrategy.calDiscount(order);
+  }
+}
+
+```
+
+重构之后的代码就没有了if-else分支判断语句了。实际上,这得益于策略工厂类。在工厂类中,我们用Map来缓存策略,根据type直接从Map中获取对应的策略,从而避免if-else分支判断逻辑。等后面讲到使用状态模式来避免分支判断逻辑的时候,你会发现,它们使用的是同样的套路。本质上都是借助“查表法”,根据type查表(代码中的strategies就是表)替代根据type分支判断。
+
+但是,如果业务场景需要每次都创建不同的策略对象,我们就要用另外一种工厂类的实现方式了。具体的代码如下所示:
+
+```
+public class DiscountStrategyFactory {
+  public static DiscountStrategy getDiscountStrategy(OrderType type) {
+    if (type == null) {
+      throw new IllegalArgumentException("Type should not be null.");
+    }
+    if (type.equals(OrderType.NORMAL)) {
+      return new NormalDiscountStrategy();
+    } else if (type.equals(OrderType.GROUPON)) {
+      return new GrouponDiscountStrategy();
+    } else if (type.equals(OrderType.PROMOTION)) {
+      return new PromotionDiscountStrategy();
+    }
+    return null;
+  }
+}
+
+```
+
+这种实现方式相当于把原来的if-else分支逻辑,从OrderService类中转移到了工厂类中,实际上并没有真正将它移除。关于这个问题如何解决,我今天先暂时卖个关子。你可以在留言区说说你的想法,我在下一节课中再讲解。
+
+## 重点回顾
+
+好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
+
+策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
+
+策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的。
+
+*   策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。
+*   策略的创建由工厂类来完成,封装策略创建的细节。
+*   策略模式包含一组策略可选,客户端代码如何选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。
+
+除此之外,我们还可以通过策略模式来移除if-else分支判断。实际上,这得益于策略工厂类,更本质上点讲,是借助“查表法”,根据type查表替代根据type分支判断。
+
+## 课堂讨论
+
+今天我们讲到,在策略工厂类中,如果每次都要返回新的策略对象,我们还是需要在工厂类中编写if-else分支判断逻辑,那这个问题该如何解决呢?
+
+欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。
+    

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 258 - 0
docs/215132.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 395 - 0
docs/216278.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 227 - 0
docs/217395.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 505 - 0
docs/218375.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 204 - 0
docs/219290.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 350 - 0
docs/219964.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 272 - 0
docs/221269.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 398 - 0
docs/221852.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 249 - 0
docs/222762.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 201 - 0
docs/223947.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 138 - 0
docs/224549.md


+ 359 - 0
docs/225904.md

@@ -0,0 +1,359 @@
+# 72 | 解释器模式:如何设计实现一个自定义接口告警规则功能?
+
+上一节课,我们学习了命令模式。命令模式将请求封装成对象,方便作为函数参数传递和赋值给变量。它主要的应用场景是给命令的执行附加功能,换句话说,就是控制命令的执行,比如,排队、异步、延迟执行命令、给命令执行记录日志、撤销重做命令等等。总体上来讲,命令模式的应用范围并不广。
+
+今天,我们来学习解释器模式,它用来描述如何构建一个简单的“语言”解释器。比起命令模式,解释器模式更加小众,只在一些特定的领域会被用到,比如编译器、规则引擎、正则表达式。所以,解释器模式也不是我们学习的重点,你稍微了解一下就可以了。
+
+话不多说,让我们正式开始今天的学习吧!
+
+## 解释器模式的原理和实现
+
+解释器模式的英文翻译是Interpreter Design Pattern。在GoF的《设计模式》一书中,它是这样定义的:
+
+> Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar.
+
+翻译成中文就是:解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
+
+看了定义,你估计会一头雾水,因为这里面有很多我们平时开发中很少接触的概念,比如“语言”“语法”“解释器”。实际上,这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。
+
+要想了解“语言”表达的信息,我们就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写“句子”(专业点的叫法应该是“表达式”),阅读者根据语法规则来阅读“句子”,这样才能做到信息的正确传递。而我们要讲的解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。
+
+为了让你更好地理解定义,我举一个比较贴近生活的例子来解释一下。
+
+实际上,理解这个概念,我们可以类比中英文翻译。我们知道,把英文翻译成中文是有一定规则的。这个规则就是定义中的“语法”。我们开发一个类似Google Translate这样的翻译器,这个翻译器能够根据语法规则,将输入的中文翻译成英文。这里的翻译器就是解释器模式定义中的“解释器”。
+
+刚刚翻译器这个例子比较贴近生活,现在,我们再举个更加贴近编程的例子。
+
+假设我们定义了一个新的加减乘除计算“语言”,语法规则如下:
+
+*   运算符只包含加、减、乘、除,并且没有优先级的概念;
+*   表达式(也就是前面提到的“句子”)中,先书写数字,后书写运算符,空格隔开;
+*   按照先后顺序,取出两个数字和一个运算符计算结果,结果重新放入数字的最头部位置,循环上述过程,直到只剩下一个数字,这个数字就是表达式最终的计算结果。
+
+我们举个例子来解释一下上面的语法规则。
+
+比如“ 8 3 2 4 - + \* ”这样一个表达式,我们按照上面的语法规则来处理,取出数字“8 3”和“-”运算符,计算得到5,于是表达式就变成了“ 5 2 4 + \* ”。然后,我们再取出“ 5 2 ”和“ + ”运算符,计算得到7,表达式就变成了“ 7 4 \* ”。最后,我们取出“ 7 4”和“ \* ”运算符,最终得到的结果就是28。
+
+看懂了上面的语法规则,我们将它用代码实现出来,如下所示。代码非常简单,用户按照上面的规则书写表达式,传递给interpret()函数,就可以得到最终的计算结果。
+
+```
+public class ExpressionInterpreter {
+  private Deque<Long> numbers = new LinkedList<>();
+
+  public long interpret(String expression) {
+    String[] elements = expression.split(" ");
+    int length = elements.length;
+    for (int i = 0; i < (length+1)/2; ++i) {
+      numbers.addLast(Long.parseLong(elements[i]));
+    }
+
+    for (int i = (length+1)/2; i < length; ++i) {
+      String operator = elements[i];
+      boolean isValid = "+".equals(operator) || "-".equals(operator)
+              || "*".equals(operator) || "/".equals(operator);
+      if (!isValid) {
+        throw new RuntimeException("Expression is invalid: " + expression);
+      }
+
+      long number1 = numbers.pollFirst();
+      long number2 = numbers.pollFirst();
+      long result = 0;
+      if (operator.equals("+")) {
+        result = number1 + number2;
+      } else if (operator.equals("-")) {
+        result = number1 - number2;
+      } else if (operator.equals("*")) {
+        result = number1 * number2;
+      } else if (operator.equals("/")) {
+        result = number1 / number2;
+      }
+      numbers.addFirst(result);
+    }
+
+    if (numbers.size() != 1) {
+      throw new RuntimeException("Expression is invalid: " + expression);
+    }
+
+    return numbers.pop();
+  }
+}
+
+```
+
+在上面的代码实现中,语法规则的解析逻辑(第23、25、27、29行)都集中在一个函数中,对于简单的语法规则的解析,这样的设计就足够了。但是,对于复杂的语法规则的解析,逻辑复杂,代码量多,所有的解析逻辑都耦合在一个函数中,这样显然是不合适的。这个时候,我们就要考虑拆分代码,将解析逻辑拆分到独立的小类中。
+
+该怎么拆分呢?我们可以借助解释器模式。
+
+解释器模式的代码实现比较灵活,没有固定的模板。我们前面也说过,应用设计模式主要是应对代码的复杂性,实际上,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分成一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。
+
+前面定义的语法规则有两类表达式,一类是数字,一类是运算符,运算符又包括加减乘除。利用解释器模式,我们把解析的工作拆分到NumberExpression、AdditionExpression、SubstractionExpression、MultiplicationExpression、DivisionExpression这样五个解析类中。
+
+按照这个思路,我们对代码进行重构,重构之后的代码如下所示。当然,因为加减乘除表达式的解析比较简单,利用解释器模式的设计思路,看起来有点过度设计。不过呢,这里我主要是为了解释原理,你明白意思就好,不用过度细究这个例子。
+
+```
+public interface Expression {
+  long interpret();
+}
+
+public class NumberExpression implements Expression {
+  private long number;
+
+  public NumberExpression(long number) {
+    this.number = number;
+  }
+
+  public NumberExpression(String number) {
+    this.number = Long.parseLong(number);
+  }
+
+  @Override
+  public long interpret() {
+    return this.number;
+  }
+}
+
+public class AdditionExpression implements Expression {
+  private Expression exp1;
+  private Expression exp2;
+
+  public AdditionExpression(Expression exp1, Expression exp2) {
+    this.exp1 = exp1;
+    this.exp2 = exp2;
+  }
+
+  @Override
+  public long interpret() {
+    return exp1.interpret() + exp2.interpret();
+  }
+}
+// SubstractionExpression/MultiplicationExpression/DivisionExpression与AdditionExpression代码结构类似,这里就省略了
+
+public class ExpressionInterpreter {
+  private Deque<Expression> numbers = new LinkedList<>();
+
+  public long interpret(String expression) {
+    String[] elements = expression.split(" ");
+    int length = elements.length;
+    for (int i = 0; i < (length+1)/2; ++i) {
+      numbers.addLast(new NumberExpression(elements[i]));
+    }
+
+    for (int i = (length+1)/2; i < length; ++i) {
+      String operator = elements[i];
+      boolean isValid = "+".equals(operator) || "-".equals(operator)
+              || "*".equals(operator) || "/".equals(operator);
+      if (!isValid) {
+        throw new RuntimeException("Expression is invalid: " + expression);
+      }
+
+      Expression exp1 = numbers.pollFirst();
+      Expression exp2 = numbers.pollFirst();
+      Expression combinedExp = null;
+      if (operator.equals("+")) {
+        combinedExp = new AdditionExpression(exp1, exp2);
+      } else if (operator.equals("-")) {
+        combinedExp = new AdditionExpression(exp1, exp2);
+      } else if (operator.equals("*")) {
+        combinedExp = new AdditionExpression(exp1, exp2);
+      } else if (operator.equals("/")) {
+        combinedExp = new AdditionExpression(exp1, exp2);
+      }
+      long result = combinedExp.interpret();
+      numbers.addFirst(new NumberExpression(result));
+    }
+
+    if (numbers.size() != 1) {
+      throw new RuntimeException("Expression is invalid: " + expression);
+    }
+
+    return numbers.pop().interpret();
+  }
+}
+
+```
+
+## 解释器模式实战举例
+
+接下来,我们再来看一个更加接近实战的例子,也就是咱们今天标题中的问题:如何实现一个自定义接口告警规则功能?
+
+在我们平时的项目开发中,监控系统非常重要,它可以时刻监控业务系统的运行情况,及时将异常报告给开发者。比如,如果每分钟接口出错数超过100,监控系统就通过短信、微信、邮件等方式发送告警给开发者。
+
+一般来讲,监控系统支持开发者自定义告警规则,比如我们可以用下面这样一个表达式,来表示一个告警规则,它表达的意思是:每分钟API总出错数超过100或者每分钟API总调用数超过10000就触发告警。
+
+```
+api_error_per_minute > 100 || api_count_per_minute > 10000
+
+```
+
+在监控系统中,告警模块只负责根据统计数据和告警规则,判断是否触发告警。至于每分钟API接口出错数、每分钟接口调用数等统计数据的计算,是由其他模块来负责的。其他模块将统计数据放到一个Map中(数据的格式如下所示),发送给告警模块。接下来,我们只关注告警模块。
+
+```
+Map<String, Long> apiStat = new HashMap<>();
+apiStat.put("api_error_per_minute", 103);
+apiStat.put("api_count_per_minute", 987);
+
+```
+
+为了简化讲解和代码实现,我们假设自定义的告警规则只包含“||、&&、>、<、==”这五个运算符,其中,“>、<、==”运算符的优先级高于“||、&&”运算符,“&&”运算符优先级高于“||”。在表达式中,任意元素之间需要通过空格来分隔。除此之外,用户可以自定义要监控的key,比如前面的api\_error\_per\_minute、api\_count\_per\_minute。
+
+那如何实现上面的需求呢?我写了一个骨架代码,如下所示,其中的核心的实现我没有给出,你可以当作面试题,自己试着去补全一下,然后再看我的讲解。
+
+```
+public class AlertRuleInterpreter {
+
+  // key1 > 100 && key2 < 1000 || key3 == 200
+  public AlertRuleInterpreter(String ruleExpression) {
+    //TODO:由你来完善
+  }
+
+  //<String, Long> apiStat = new HashMap<>();
+  //apiStat.put("key1", 103);
+  //apiStat.put("key2", 987);
+  public boolean interpret(Map<String, Long> stats) {
+    //TODO:由你来完善
+  }
+
+}
+
+public class DemoTest {
+  public static void main(String[] args) {
+    String rule = "key1 > 100 && key2 < 30 || key3 < 100 || key4 == 88";
+    AlertRuleInterpreter interpreter = new AlertRuleInterpreter(rule);
+    Map<String, Long> stats = new HashMap<>();
+    stats.put("key1", 101l);
+    stats.put("key3", 121l);
+    stats.put("key4", 88l);
+    boolean alert = interpreter.interpret(stats);
+    System.out.println(alert);
+  }
+}
+
+```
+
+实际上,我们可以把自定义的告警规则,看作一种特殊“语言”的语法规则。我们实现一个解释器,能够根据规则,针对用户输入的数据,判断是否触发告警。利用解释器模式,我们把解析表达式的逻辑拆分到各个小类中,避免大而复杂的大类的出现。按照这个实现思路,我把刚刚的代码补全,如下所示,你可以拿你写的代码跟我写的对比一下。
+
+```
+public interface Expression {
+  boolean interpret(Map<String, Long> stats);
+}
+
+public class GreaterExpression implements Expression {
+  private String key;
+  private long value;
+
+  public GreaterExpression(String strExpression) {
+    String[] elements = strExpression.trim().split("\\s+");
+    if (elements.length != 3 || !elements[1].trim().equals(">")) {
+      throw new RuntimeException("Expression is invalid: " + strExpression);
+    }
+    this.key = elements[0].trim();
+    this.value = Long.parseLong(elements[2].trim());
+  }
+
+  public GreaterExpression(String key, long value) {
+    this.key = key;
+    this.value = value;
+  }
+
+  @Override
+  public boolean interpret(Map<String, Long> stats) {
+    if (!stats.containsKey(key)) {
+      return false;
+    }
+    long statValue = stats.get(key);
+    return statValue > value;
+  }
+}
+
+// LessExpression/EqualExpression跟GreaterExpression代码类似,这里就省略了
+
+public class AndExpression implements Expression {
+  private List<Expression> expressions = new ArrayList<>();
+
+  public AndExpression(String strAndExpression) {
+    String[] strExpressions = strAndExpression.split("&&");
+    for (String strExpr : strExpressions) {
+      if (strExpr.contains(">")) {
+        expressions.add(new GreaterExpression(strExpr));
+      } else if (strExpr.contains("<")) {
+        expressions.add(new LessExpression(strExpr));
+      } else if (strExpr.contains("==")) {
+        expressions.add(new EqualExpression(strExpr));
+      } else {
+        throw new RuntimeException("Expression is invalid: " + strAndExpression);
+      }
+    }
+  }
+
+  public AndExpression(List<Expression> expressions) {
+    this.expressions.addAll(expressions);
+  }
+
+  @Override
+  public boolean interpret(Map<String, Long> stats) {
+    for (Expression expr : expressions) {
+      if (!expr.interpret(stats)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+}
+
+public class OrExpression implements Expression {
+  private List<Expression> expressions = new ArrayList<>();
+
+  public OrExpression(String strOrExpression) {
+    String[] andExpressions = strOrExpression.split("\\|\\|");
+    for (String andExpr : andExpressions) {
+      expressions.add(new AndExpression(andExpr));
+    }
+  }
+
+  public OrExpression(List<Expression> expressions) {
+    this.expressions.addAll(expressions);
+  }
+
+  @Override
+  public boolean interpret(Map<String, Long> stats) {
+    for (Expression expr : expressions) {
+      if (expr.interpret(stats)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+public class AlertRuleInterpreter {
+  private Expression expression;
+
+  public AlertRuleInterpreter(String ruleExpression) {
+    this.expression = new OrExpression(ruleExpression);
+  }
+
+  public boolean interpret(Map<String, Long> stats) {
+    return expression.interpret(stats);
+  }
+} 
+
+```
+
+## 重点回顾
+
+好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
+
+解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。实际上,这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。
+
+要想了解“语言”要表达的信息,我们就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写“句子”(专业点的叫法应该是“表达式”),阅读者根据语法规则来阅读“句子”,这样才能做到信息的正确传递。而我们要讲的解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。
+
+解释器模式的代码实现比较灵活,没有固定的模板。我们前面说过,应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。
+
+## 课堂讨论
+
+1.在你过往的项目经历或阅读源码的时候,有没有用到或者见过解释器模式呢?  
+2.在告警规则解析的例子中,如果我们要在表达式中支持括号“()”,那如何对代码进行重构呢?你可以把它当作练习,试着编写一下代码。
+
+欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。
+    

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 193 - 0
docs/226710.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 229 - 0
docs/227452.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 86 - 0
docs/229157.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 356 - 0
docs/229996.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 233 - 0
docs/230708.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 96 - 0
docs/232061.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 66 - 0
docs/232427.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 100 - 0
docs/232687.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 88 - 0
docs/233742.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 333 - 0
docs/234758.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 268 - 0
docs/235334.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 107 - 0
docs/236935.md


+ 310 - 0
docs/237810.md

@@ -0,0 +1,310 @@
+# 85 | 开源实战四(中):剖析Spring框架中用来支持扩展的两种设计模式
+
+上一节课中,我们学习了Spring框架背后蕴藏的一些经典设计思想,比如约定优于配置、低侵入松耦合、模块化轻量级等等。我们可以将这些设计思想借鉴到其他框架开发中,在大的设计层面提高框架的代码质量。这也是我们在专栏中讲解这部分内容的原因。
+
+除了上一节课中讲到的设计思想,实际上,可扩展也是大部分框架应该具备的一个重要特性。所谓的框架可扩展,我们之前也提到过,意思就是,框架使用者在不修改框架源码的情况下,基于扩展点定制扩展新的功能。
+
+前面在理论部分,我们也讲到,常用来实现扩展特性的设计模式有:观察者模式、模板模式、职责链模式、策略模式等。今天,我们再剖析Spring框架为了支持可扩展特性用的2种设计模式:观察者模式和模板模式。
+
+话不多说,让我们正式开始今天的学习吧!
+
+## 观察者模式在Spring中的应用
+
+在前面我们讲到,Java、Google Guava都提供了观察者模式的实现框架。Java提供的框架比较简单,只包含java.util.Observable和java.util.Observer两个类。Google Guava提供的框架功能比较完善和强大:通过EventBus事件总线来实现观察者模式。实际上,Spring也提供了观察者模式的实现框架。今天,我们就再来讲一讲它。
+
+Spring中实现的观察者模式包含三部分:Event事件(相当于消息)、Listener监听者(相当于观察者)、Publisher发送者(相当于被观察者)。我们通过一个例子来看下,Spring提供的观察者模式是怎么使用的。代码如下所示:
+
+```
+// Event事件
+public class DemoEvent extends ApplicationEvent {
+  private String message;
+
+  public DemoEvent(Object source, String message) {
+    super(source);
+  }
+
+  public String getMessage() {
+    return this.message;
+  }
+}
+
+// Listener监听者
+@Component
+public class DemoListener implements ApplicationListener<DemoEvent> {
+  @Override
+  public void onApplicationEvent(DemoEvent demoEvent) {
+    String message = demoEvent.getMessage();
+    System.out.println(message);
+  }
+}
+
+// Publisher发送者
+@Component
+public class DemoPublisher {
+  @Autowired
+  private ApplicationContext applicationContext;
+
+  public void publishEvent(DemoEvent demoEvent) {
+    this.applicationContext.publishEvent(demoEvent);
+  }
+}
+
+```
+
+从代码中,我们可以看出,框架使用起来并不复杂,主要包含三部分工作:定义一个继承ApplicationEvent的事件(DemoEvent);定义一个实现了ApplicationListener的监听器(DemoListener);定义一个发送者(DemoPublisher),发送者调用ApplicationContext来发送事件消息。
+
+其中,ApplicationEvent和ApplicationListener的代码实现都非常简单,内部并不包含太多属性和方法。实际上,它们最大的作用是做类型标识之用(继承自ApplicationEvent的类是事件,实现ApplicationListener的类是监听器)。
+
+```
+public abstract class ApplicationEvent extends EventObject {
+  private static final long serialVersionUID = 7099057708183571937L;
+  private final long timestamp = System.currentTimeMillis();
+
+  public ApplicationEvent(Object source) {
+    super(source);
+  }
+
+  public final long getTimestamp() {
+    return this.timestamp;
+  }
+}
+
+public class EventObject implements java.io.Serializable {
+    private static final long serialVersionUID = 5516075349620653480L;
+    protected transient Object  source;
+
+    public EventObject(Object source) {
+        if (source == null)
+            throw new IllegalArgumentException("null source");
+        this.source = source;
+    }
+
+    public Object getSource() {
+        return source;
+    }
+
+    public String toString() {
+        return getClass().getName() + "[source=" + source + "]";
+    }
+}
+
+public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
+  void onApplicationEvent(E var1);
+}
+
+```
+
+在前面讲到观察者模式的时候,我们提到,观察者需要事先注册到被观察者(JDK的实现方式)或者事件总线(EventBus的实现方式)中。那在Spring的实现中,观察者注册到了哪里呢?又是如何注册的呢?
+
+我想你应该猜到了,我们把观察者注册到了ApplicationContext对象中。这里的ApplicationContext就相当于Google EventBus框架中的“事件总线”。不过,稍微提醒一下,ApplicationContext这个类并不只是为观察者模式服务的。它底层依赖BeanFactory(IOC的主要实现类),提供应用启动、运行时的上下文信息,是访问这些信息的最顶层接口。
+
+实际上,具体到源码来说,ApplicationContext只是一个接口,具体的代码实现包含在它的实现类AbstractApplicationContext中。我把跟观察者模式相关的代码,摘抄到了下面。你只需要关注它是如何发送事件和注册监听者就好,其他细节不需要细究。
+
+```
+public abstract class AbstractApplicationContext extends ... {
+  private final Set<ApplicationListener<?>> applicationListeners;
+  
+  public AbstractApplicationContext() {
+    this.applicationListeners = new LinkedHashSet();
+    //...
+  }
+  
+  public void publishEvent(ApplicationEvent event) {
+    this.publishEvent(event, (ResolvableType)null);
+  }
+
+  public void publishEvent(Object event) {
+    this.publishEvent(event, (ResolvableType)null);
+  }
+
+  protected void publishEvent(Object event, ResolvableType eventType) {
+    //...
+    Object applicationEvent;
+    if (event instanceof ApplicationEvent) {
+      applicationEvent = (ApplicationEvent)event;
+    } else {
+      applicationEvent = new PayloadApplicationEvent(this, event);
+      if (eventType == null) {
+        eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
+      }
+    }
+
+    if (this.earlyApplicationEvents != null) {
+      this.earlyApplicationEvents.add(applicationEvent);
+    } else {
+      this.getApplicationEventMulticaster().multicastEvent(
+            (ApplicationEvent)applicationEvent, eventType);
+    }
+
+    if (this.parent != null) {
+      if (this.parent instanceof AbstractApplicationContext) {
+        ((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
+      } else {
+        this.parent.publishEvent(event);
+      }
+    }
+  }
+  
+  public void addApplicationListener(ApplicationListener<?> listener) {
+    Assert.notNull(listener, "ApplicationListener must not be null");
+    if (this.applicationEventMulticaster != null) {
+    this.applicationEventMulticaster.addApplicationListener(listener);
+    } else {
+      this.applicationListeners.add(listener);
+    }  
+  }
+  
+  public Collection<ApplicationListener<?>> getApplicationListeners() {
+    return this.applicationListeners;
+  }
+  
+  protected void registerListeners() {
+    Iterator var1 = this.getApplicationListeners().iterator();
+
+    while(var1.hasNext()) {
+      ApplicationListener<?> listener = (ApplicationListener)var1.next();     this.getApplicationEventMulticaster().addApplicationListener(listener);
+    }
+
+    String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);
+    String[] var7 = listenerBeanNames;
+    int var3 = listenerBeanNames.length;
+
+    for(int var4 = 0; var4 < var3; ++var4) {
+      String listenerBeanName = var7[var4];
+      this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
+    }
+
+    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
+    this.earlyApplicationEvents = null;
+    if (earlyEventsToProcess != null) {
+      Iterator var9 = earlyEventsToProcess.iterator();
+
+      while(var9.hasNext()) {
+        ApplicationEvent earlyEvent = (ApplicationEvent)var9.next();
+        this.getApplicationEventMulticaster().multicastEvent(earlyEvent);
+      }
+    }
+  }
+}
+
+```
+
+从上面的代码中,我们发现,真正的消息发送,实际上是通过ApplicationEventMulticaster这个类来完成的。这个类的源码我只摘抄了最关键的一部分,也就是multicastEvent()这个消息发送函数。不过,它的代码也并不复杂,我就不多解释了。这里我稍微提示一下,它通过线程池,支持异步非阻塞、同步阻塞这两种类型的观察者模式。
+
+```
+public void multicastEvent(ApplicationEvent event) {
+  this.multicastEvent(event, this.resolveDefaultEventType(event));
+}
+
+public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
+  ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
+  Iterator var4 = this.getApplicationListeners(event, type).iterator();
+
+  while(var4.hasNext()) {
+    final ApplicationListener<?> listener = (ApplicationListener)var4.next();
+    Executor executor = this.getTaskExecutor();
+    if (executor != null) {
+      executor.execute(new Runnable() {
+        public void run() {
+          SimpleApplicationEventMulticaster.this.invokeListener(listener, event);
+        }
+      });
+    } else {
+      this.invokeListener(listener, event);
+    }
+  }
+
+}
+
+```
+
+借助Spring提供的观察者模式的骨架代码,如果我们要在Spring下实现某个事件的发送和监听,只需要做很少的工作,定义事件、定义监听器、往ApplicationContext中发送事件就可以了,剩下的工作都由Spring框架来完成。实际上,这也体现了Spring框架的扩展性,也就是在不需要修改任何代码的情况下,扩展新的事件和监听。
+
+## 模板模式在Spring中的应用
+
+刚刚讲的是观察者模式在Spring中的应用,现在我们再讲下模板模式。
+
+我们来看下一下经常在面试中被问到的一个问题:请你说下Spring Bean的创建过程包含哪些主要的步骤。这其中就涉及模板模式。它也体现了Spring的扩展性。利用模板模式,Spring能让用户定制Bean的创建过程。
+
+Spring Bean的创建过程,可以大致分为两大步:对象的创建和对象的初始化。
+
+对象的创建是通过反射来动态生成对象,而不是new方法。不管是哪种方式,说白了,总归还是调用构造函数来生成对象,没有什么特殊的。对象的初始化有两种实现方式。一种是在类中自定义一个初始化函数,并且通过配置文件,显式地告知Spring,哪个函数是初始化函数。我举了一个例子解释一下。如下所示,在配置文件中,我们通过init-method属性来指定初始化函数。
+
+```
+public class DemoClass {
+  //...
+  
+  public void initDemo() {
+    //...初始化..
+  }
+}
+
+// 配置:需要通过init-method显式地指定初始化方法
+<bean id="demoBean" class="com.xzg.cd.DemoClass" init-method="initDemo"></bean>
+
+```
+
+这种初始化方式有一个缺点,初始化函数并不固定,由用户随意定义,这就需要Spring通过反射,在运行时动态地调用这个初始化函数。而反射又会影响代码执行的性能,那有没有替代方案呢?
+
+Spring提供了另外一个定义初始化函数的方法,那就是让类实现Initializingbean接口。这个接口包含一个固定的初始化函数定义(afterPropertiesSet()函数)。Spring在初始化Bean的时候,可以直接通过bean.afterPropertiesSet()的方式,调用Bean对象上的这个函数,而不需要使用反射来调用了。我举个例子解释一下,代码如下所示。
+
+```
+public class DemoClass implements InitializingBean{
+  @Override
+  public void afterPropertiesSet() throws Exception {
+    //...初始化...      
+  }
+}
+
+// 配置:不需要显式地指定初始化方法
+<bean id="demoBean" class="com.xzg.cd.DemoClass"></bean>
+
+```
+
+尽管这种实现方式不会用到反射,执行效率提高了,但业务代码(DemoClass)跟框架代码(InitializingBean)耦合在了一起。框架代码侵入到了业务代码中,替换框架的成本就变高了。所以,我并不是太推荐这种写法。
+
+实际上,在Spring对Bean整个生命周期的管理中,还有一个跟初始化相对应的过程,那就是Bean的销毁过程。我们知道,在Java中,对象的回收是通过JVM来自动完成的。但是,我们可以在将Bean正式交给JVM垃圾回收前,执行一些销毁操作(比如关闭文件句柄等等)。
+
+销毁过程跟初始化过程非常相似,也有两种实现方式。一种是通过配置destroy-method指定类中的销毁函数,另一种是让类实现DisposableBean接口。因为destroy-method、DisposableBean跟init-method、InitializingBean非常相似,所以,这部分我们就不详细讲解了,你可以自行研究下。
+
+实际上,Spring针对对象的初始化过程,还做了进一步的细化,将它拆分成了三个小步骤:初始化前置操作、初始化、初始化后置操作。其中,中间的初始化操作就是我们刚刚讲的那部分,初始化的前置和后置操作,定义在接口BeanPostProcessor中。BeanPostProcessor的接口定义如下所示:
+
+```
+public interface BeanPostProcessor {
+  Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;
+
+  Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
+}
+
+```
+
+我们再来看下,如何通过BeanPostProcessor来定义初始化前置和后置操作?
+
+我们只需要定义一个实现了BeanPostProcessor接口的处理器类,并在配置文件中像配置普通Bean一样去配置就可以了。Spring中的ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有Bean,并把它们注册到BeanPostProcessor处理器列表中。在Spring容器创建Bean的过程中,Spring会逐一去调用这些处理器。
+
+通过上面的分析,我们基本上弄清楚了Spring Bean的整个生命周期(创建加销毁)。针对这个过程,我画了一张图,你可以结合着刚刚讲解一块看下。
+
+![](https://static001.geekbang.org/resource/image/ca/4d/cacaf86b03a9432a4885385d2869264d.jpg)
+
+不过,你可能会说,这里哪里用到了模板模式啊?模板模式不是需要定义一个包含模板方法的抽象模板类,以及定义子类实现模板方法吗?
+
+实际上,这里的模板模式的实现,并不是标准的抽象类的实现方式,而是有点类似我们前面讲到的Callback回调的实现方式,也就是将要执行的函数封装成对象(比如,初始化方法封装成InitializingBean对象),传递给模板(BeanFactory)来执行。
+
+## 重点回顾
+
+好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
+
+今天我讲到了Spring中用到的两种支持扩展的设计模式,观察者模式和模板模式。
+
+其中,观察者模式在Java、Google Guava、Spring中都有提供相应的实现代码。在平时的项目开发中,基于这些实现代码,我们可以轻松地实现一个观察者模式。
+
+Java提供的框架比较简单,只包含java.util.Observable和java.util.Observer两个类。Google Guava提供的框架功能比较完善和强大,可以通过EventBus事件总线来实现观察者模式。Spring提供了观察者模式包含Event事件、Listener监听者、Publisher发送者三部分。事件发送到ApplicationContext中,然后,ApplicationConext将消息发送给事先注册好的监听者。
+
+除此之外,我们还讲到模板模式在Spring中的一个典型应用,那就是Bean的创建过程。Bean的创建包含两个大的步骤,对象的创建和对象的初始化。其中,对象的初始化又可以分解为3个小的步骤:初始化前置操作、初始化、初始化后置操作。
+
+## 课堂讨论
+
+在Google Guava的EventBus实现中,被观察者发送消息到事件总线,事件总线根据消息的类型,将消息发送给可匹配的观察者。那在Spring提供的观察者模式的实现中,是否也支持按照消息类型匹配观察者呢?如果能,它是如何实现的?如果不能,你有什么方法可以让它支持吗?
+
+欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。
+    

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 486 - 0
docs/238418.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 126 - 0
docs/239239.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 331 - 0
docs/240147.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 679 - 0
docs/240971.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 116 - 0
docs/242314.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 88 - 0
docs/243175.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 412 - 0
docs/243961.md


+ 0 - 0
docs/245022.md


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác