cos测试完毕,图床切回原版

This commit is contained in:
youthlql
2022-03-16 01:23:41 +08:00
parent 8c89dac3bf
commit 0660078ffa
47 changed files with 737 additions and 737 deletions

View File

@@ -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"/>

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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"/>

View File

@@ -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"/>

View File

@@ -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"/>

View File

@@ -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"/>

View File

@@ -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"/>