mirror of
https://github.com/youthlql/JavaYouth.git
synced 2026-03-13 21:33:42 +08:00
cos测试完毕,图床切回原版
This commit is contained in:
@@ -198,7 +198,7 @@ public class Ostrich extends AbstractBird { //鸵鸟
|
||||
1. 这种设计思路虽然可以解决问题,但不够优美。因为除了鸵鸟之外,不会飞的鸟还有很多,比如企鹅。对于这些不会飞的鸟来说,我们都需要重写 fly() 方法,抛出异常。这样的设计,一方面,徒增了编码的工作量;另一方面,也违背了我们之后要讲的最小知识原则(Least Knowledge Principle,也叫最少知识原则或者迪米特法则),暴露不该暴露的接口给外部,增加了类使用过程中被误用的概率。
|
||||
2. 你可能又会说,那我们再通过 AbstractBird 类派生出两个更加细分的抽象类:会飞的鸟类 AbstractFlyableBird 和不会飞的鸟类 AbstractUnFlyableBird,让麻雀、乌鸦这些会飞的鸟都继承 AbstractFlyableBird,让鸵鸟、企鹅这些不会飞的鸟,都继承 AbstractUnFlyableBird 类,不就可以了吗?具体的继承关系如下图所示:
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/design_ideas/0001.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/design_ideas/0001.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ public class Ostrich extends AbstractBird { //鸵鸟
|
||||
|
||||
2. 是否会飞?是否会叫?两个行为搭配起来会产生四种情况:会飞会叫、不会飞会叫、会飞不会叫、不会飞不会叫。如果我们继续沿用刚才的设计思路,那就需要再定义四个抽象类(AbstractFlyableTweetableBird、AbstractFlyableUnTweetableBird、AbstractUnFlyableTweetableBird、AbstractUnFlyableUnTweetableBird)。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/design_ideas/0002.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/design_ideas/0002.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -376,7 +376,7 @@ demofunction(client);
|
||||
|
||||
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/design_ideas/0003.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/design_ideas/0003.png"/>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -724,7 +724,7 @@ public class UserRepo {
|
||||
|
||||
前面也提到,“高内聚”有助于“松耦合”,同理,“低内聚”也会导致“紧耦合”。关于这一点,我画了一张对比图来解释。图中左边部分的代码结构是“高内聚、松耦合”;右边部分正好相反,是“低内聚、紧耦合”。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/design_principles/0001.png" />
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/design_principles/0001.png" />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -727,7 +727,7 @@ public class BeansFactory {
|
||||
1. 在平时的开发中,创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成。我的问题是,什么情况下这种方式就不适用了,就需要采用建造者模式来创建对象呢?你可以先思考一下,下面我通过一个例子来带你看一下。
|
||||
2. 假设有这样一道设计面试题:我们需要定义一个资源池配置类 ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。现在,请你编写代码实现这个 ResourcePoolConfig 类。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/creational/03.02/0001.png" />
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/creational/03.02/0001.png" />
|
||||
|
||||
|
||||
|
||||
@@ -1003,7 +1003,7 @@ r.setHeight(3); // r is valid
|
||||
2. 如果你熟悉的是 Java 语言,可以直接使用语言中提供的 HashMap 容器来实现。其中,HashMap 的 key 为搜索关键词,value 为关键词详细信息(比如搜索次数)。我们只需要将数据从数据库中读取出来,放入 HashMap 就可以了。
|
||||
3. 不过,我们还有另外一个系统 B,专门用来分析搜索日志,定期(比如间隔 10 分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,我们对 v2 版本的数据进行更新,得到 v3 版本的数据。这里我们假设只有更新和新添关键词,没有删除关键词的行为。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/creational/03.02/0002.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/creational/03.02/0002.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -1150,15 +1150,15 @@ public class Demo {
|
||||
|
||||
我们来看,在内存中,用散列表组织的搜索关键词信息是如何存储的。我画了一张示意图,大致结构如下所示。从图中我们可以发现,散列表索引中,每个结点存储的 key 是搜索关键词,value 是 SearchWord 对象的内存地址。SearchWord 对象本身存储在散列表之外的内存空间中。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/creational/03.02/0003.png" />
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/creational/03.02/0003.png" />
|
||||
|
||||
浅拷贝和深拷贝的区别在于,浅拷贝只会复制图中的索引(散列表),不会复制数据(SearchWord 对象)本身。相反,深拷贝不仅仅会复制索引,还会复制数据本身。浅拷贝得到的对象(newKeywords)跟原始对象(currentKeywords)共享数据(SearchWord 对象),而深拷贝得到的是一份完完全全独立的对象。具体的对比如下图所示:
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/creational/03.02/0004.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/creational/03.02/0004.png"/>
|
||||
|
||||
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/creational/03.02/0005.png" />
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/creational/03.02/0005.png" />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -608,7 +608,7 @@ IUserController userController = (IUserController) proxy.createProxy(new UserCon
|
||||
|
||||
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.01/0001.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.01/0001.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -616,7 +616,7 @@ IUserController userController = (IUserController) proxy.createProxy(new UserCon
|
||||
|
||||
传统方法对应的类图
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.01/0007.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.01/0007.png"/>
|
||||
|
||||
1. 扩展性问题(**类爆炸**),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
|
||||
2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
|
||||
@@ -933,7 +933,7 @@ public class DriverManager {
|
||||
|
||||
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.01/0002.png">
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.01/0002.png">
|
||||
|
||||
|
||||
|
||||
@@ -1112,7 +1112,7 @@ public class TrivialNotification extends Notification {
|
||||
|
||||
### 方案一
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.01/0003.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.01/0003.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -1127,7 +1127,7 @@ public class TrivialNotification extends Notification {
|
||||
|
||||
前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.01/0004.png">
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.01/0004.png">
|
||||
|
||||
|
||||
|
||||
@@ -1142,7 +1142,7 @@ public class TrivialNotification extends Notification {
|
||||
|
||||
### 装饰器模式代码
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.01/0005.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.01/0005.png"/>
|
||||
|
||||
#### Drink【抽象类-主体Component】
|
||||
|
||||
@@ -1390,7 +1390,7 @@ Java IO 类库非常庞大和复杂,有几十个类,负责 IO 数据的读
|
||||
|
||||
针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类。具体如下所示:
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.01/0006.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.01/0006.png"/>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ date: 2021-07-05 16:51:58
|
||||
|
||||
### 传统方案
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.02/0001.png" />
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.02/0001.png" />
|
||||
|
||||
1. 在 ClientTest 的 main 方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程 混乱,没有清晰的过程 不利于在 ClientTest 中,去维护对子系统的操作
|
||||
|
||||
@@ -623,7 +623,7 @@ public class Demo {
|
||||
|
||||
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/structural_type/04.02/0002.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/structural_type/04.02/0002.png"/>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ date: 2021-07-14 16:51:58
|
||||
|
||||
### 方案一
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.01/0001.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.01/0001.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -721,11 +721,11 @@ public DObserver{
|
||||
1. Guava EventBus 的功能我们已经讲清楚了,总体上来说,还是比较简单的。接下来,我们就重复造轮子,“山寨”一个 EventBus 出来。
|
||||
2. 我们重点来看,EventBus 中两个核心函数 register() 和 post() 的实现原理。弄懂了它们,基本上就弄懂了整个 EventBus 框架。下面两张图是这两个函数的实现原理图。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.01/0002.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.01/0002.png"/>
|
||||
|
||||
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.01/0003.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.01/0003.png"/>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1171,7 +1171,7 @@ public class SensitiveWordFilter {
|
||||
|
||||
Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持过滤器功能。为了帮助你理解,我画了一张示意图阐述它的工作原理,如下所示。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.02/0001.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.02/0001.png"/>
|
||||
|
||||
在实际项目中,我们该如何使用 Servlet Filter 呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中。Web 容器启动的时候,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由 Servlet 来处理。
|
||||
|
||||
@@ -1279,7 +1279,7 @@ ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,
|
||||
1. 刚刚讲了 Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对 HTTP 请求进行拦截处理。
|
||||
2. 它们不同之处在于,Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.02/0002.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.02/0002.png"/>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ date: 2021-08-02 15:51:58
|
||||
4. 实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。
|
||||
5. 为了方便接下来的讲解,我对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.03/0001.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.03/0001.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ public class MarioStateMachine {
|
||||
1. 实际上,上面这种实现方法有点类似 hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。接下来,我们就一块儿来看下,如何利用查表法来补全骨架代码。
|
||||
2. 实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.03/0002.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.03/0002.png"/>
|
||||
|
||||
3. 相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 transitionTable 和 actionTable 两个二维数组即可。实际上,如果我们把这两个二维数组存储在配置文件中,当需要修改状态机时,我们甚至可以不修改任何代码,只需要修改配置文件就可以了。具体的代码如下所示:
|
||||
|
||||
@@ -511,7 +511,7 @@ public class MarioStateMachine {
|
||||
2. 在开篇中我们讲到,它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
|
||||
3. 迭代器是用来遍历容器的,所以,一个完整的迭代器模式一般会涉及**容器和容器迭代器**部分两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。对于迭代器模式,我画了一张简单的类图,你可以看一看,先有个大致的印象。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.03/0003.png">
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.03/0003.png">
|
||||
|
||||
|
||||
|
||||
@@ -629,7 +629,7 @@ public class Demo {
|
||||
2. 结合刚刚的例子,我们来总结一下迭代器的设计思路。总结下来就三句话:迭代器中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。待遍历的容器对象通过依赖注入传递到迭代器类中。容器通过 iterator() 方法来创建迭代器。
|
||||
3. 这里我画了一张类图,如下所示。实际上就是对上面那张类图的细化,你可以结合着一块看。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.03/0004.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.03/0004.png"/>
|
||||
|
||||
|
||||
|
||||
@@ -752,7 +752,7 @@ public class Demo {
|
||||
4. 为了保持数组存储数据的连续性,数组的删除操作会涉及元素的搬移。当执行到第 57 行代码的时候,我们从数组中将元素 a 删除掉,b、c、d 三个元素会依次往前搬移一位,这就会导致游标本来指向元素 b,现在变成了指向元素 c。原本在执行完第 56 行代码之后,我们还可以遍历到 b、c、d 三个元素,但在执行完第 57 行代码之后,我们只能遍历到 c、d 两个元素,b 遍历不到了。
|
||||
5. 对于上面的描述,我画了一张图,你可以对照着理解。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.03/0005.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.03/0005.png"/>
|
||||
|
||||
6. 不过,如果第 57 行代码删除的不是游标前面的元素(元素 a)以及游标所在位置的元素(元素 b),而是游标后面的元素(元素 c 和 d),这样就不会存在任何问题了,不会存在某个元素遍历不到的情况了。
|
||||
7. 所以,我们前面说,在遍历的过程中删除集合元素,结果是不可预期的,有时候没问题(删除元素 c 或 d),有时候就有问题(删除元素 a 或 b),这个要视情况而定(到底删除的是哪个位置的元素),就是这个意思。
|
||||
@@ -778,7 +778,7 @@ public class Demo {
|
||||
10. 跟删除情况类似,如果我们在游标的后面添加元素,就不会存在任何问题。所以,在遍历的同时添加集合元素也是一种不可预期行为。
|
||||
11. 同样,对于上面的添加元素的情况,我们也画了一张图,如下所示,你可以对照着理解。
|
||||
|
||||
<img src="https://img.imlql.cn/youthlql@1.0.0/design_patterns/behavior_type/05.03/0006.png"/>
|
||||
<img src="https://npm.elemecdn.com/youthlql@1.0.0/design_patterns/behavior_type/05.03/0006.png"/>
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user