(一)访问者模式

案例:ZJJJavaBasic_2019/11/26 8:48:49_ovfqk


实际业务中使用概率是比较低的

定义
封装作用于某数据结构(如List/Set/Map等)中的各元素的操作
可以在不改变各元素的类的前提下,定义作用于这些元素的操作.
动机
以不同的方式操作复杂对象结构

访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。—— From 百科
场景:
一个数据结构(如List/Set/Map等)包含很多类型对象,比如说一个List集合里面又包含FreeCourse(免费课程实体类)和CodingCourse(实战课程实体类),一个集合的泛型是无法放下两个实体类的,这样你可以定义一个抽象课程(Course)实体类,让集合泛型是抽象课程(Course)的实体类,这样这个集合就能同时装下FreeCourse和CodingCourse 了.


数据结构与数据操作分离(数据是数据,对数据的操作是对数据的操作,将这两项分离)

优点:
增加新的操作很容易。增加新的操作就是新的访问者。
缺点:
很难增加新的数据结构.
具体元素的变更很麻烦(增加元素和删除元素里面的属性都算变更)
应用:
1.javax.lang.model.element.AnnotationValue和AnnotationValueVisitor

2.javax.lang.model.element.Element和ElementVisitor

3.javax.lang.model.type.TypeMirror和TypeVisitor

(二)模版方法模式

案例:ZJJ_JavaBasic_2019/10/06_12:25:03_z72lt


定义
定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现.
模版方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤

原则:代码复用平台。

场景:
一次性实现一个算法不变的部分,并将可变的行为留给子类来实现.
各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复.

比如说课程,前端课程和Java课程,两者都有共同的地方,就是写代码,和视频. 不同的地方就是Java课程有数据库等等,前端课程有Photoshop等等, 我们可以给共同的地方(写代码和视频抽取成一个模版),然后不同的地方让子类去实现.

优点:
模板方法模式是通过把不变行为搬移到超类(抽象方法),提高了复用性
将不同的代码放到不同的子类中,通过扩展增加新的行为
符合开闭原则

缺点:
如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大。
如果父类添加了新的抽象方法,所有子类都得改一遍

应用:
AbstractClass抽象类里面的TemplateMethod()就是模板方法。

(三)策略模式


策略模式一般不是单独来使用的,可能结合单例模式结合工厂模式(工厂模式可以用到反射的方式)结合享元模式方式

策略模式(strategy) 它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。

使用场景:
如果项目中有大量的if else 就可以考虑是否使用策略模式,或者是说对于一个对象的行为,经常要变化(需求经常变化),扩展性要求高,也要考虑下策略模式(扩展一个就是加一套算法.符合开闭原则)
优点:
策略模式的策略类为上下文定义了一系列可供重用的算法或行为,继承有助于析取出这些算法中的公共功能。另外,策略模式简化了单元测试,因为每一个算法都有自己的类,可以通过自己的接口单独测试。当不同的行为堆砌在一个类中,很难避免使用switch语句。但是将这些行为封装在一个一个独立的策略类中,可以在使用这些行为的类中消除条件语句
缺点:
基本的策略模式,选择权在客户端,具体实现转给策略模式的上下文对象。这并不好。使用策略模式和工厂类结合,可以减轻客户端的职责。但是还是不够完美,使用反射才能真正快乐。


[纯干货] 如何用Spring 原生注解 快速实现策略模式+工厂模式

https://juejin.im/post/5db0e910518825648f2ef355#heading-8


1.利用Spring特性实现策略模式

案例:ZJJ_Spring_2020/04/28_16:58:25_5cqku




(四)状态模式


在很多情况下我们对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态,也就是说行为依赖状态,即当该对象因为在外部的互动而导致他的状态发生变化,从而它的行为也会做出相应的变化。对于这种情况,我们是不能用行为来控制状态的变化,而应该站在状态的角度来思考行为,即是什么状态就要做出什么样的行为。这个就是状态模式。

所以状态模式就是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
在状态模式中我们可以减少大块的if…else语句,它是允许态转换逻辑与状态对象合成一体,但是减少if…else语句的代价就是会换来大量的类,所以状态模式势必会增加系统中类或者对象的个数。
同时状态模式是将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。但是这样就会导致系统的结构和实现都会比较复杂,如果使用不当就会导致程序的结构和代码混乱,不利于维护。

原则:单一职责原则

场景
当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,可以考虑使用状态模式了。
比如说播放视频软件,播放时候你可以单击暂停也可以点击快进, 当你停止播放视频的时候你不能点击快进,因为视频被停止了.


优点:
将不同的状态隔离,不同的状态放到不同的class类中,消除了庞大的条件分支语句(防止条件表达式过于复杂)
增加新的状态非常简单
缺点:
状态多的业务场景导致类数目增加,系统变复杂
违背开放-封闭原则

应用:
1.java.util.Iterator
2.javax.faces.lifecycle.LifeCycle#execute()

1.状态模式和策略模式的区别

状态是系统自身的固有的,调用者不能控制系统的状态转移。比如,一个请假单有“部长审批”-“经理审批”-“审批通过”-“审批不通过”等状态,请假者没有办法将一个部长都还没审批完的请假单提交给经理,这个状态转换只能系统自己完成。
策略是外界给的,策略怎么变,是调用者考虑的事情,系统只是根据所给的策略做事情。
环境角色的职责不同
两者都有一个叫做Context环境角色的类,但是两者的区别很大,策略模式的环境角色只是一个委托作用,负责算法的替换;而状态模式的环境角色不仅仅是委托行为,它还具有登记状态变化的功能,与具体的状态类协作,共同完成状态切换行为随之切换的任务。

解决问题的重点不同
策略模式旨在解决内部算法如何改变的问题,也就是将内部算法的改变对外界的影响降低到最小,它保证的是算法可以自由地切换;而状态模式旨在解决内在状态的改变而引起行为改变的问题,它的出发点是事物的状态,封装状态而暴露行为,一个对象的状态改变,从外界来看就好像是行为改变。

解决问题的方法不同
策略模式只是确保算法可以自由切换,但是什么时候用什么算法它决定不了;而状态模式对外暴露的是行为,状态的变化一般是由环境角色和具体状态共同完成的,也就是说状态模式封装了状态的变化而暴露了不同的行为或行为结果。

复杂度不同
通常策略模式比较简单,这里的简单指的是结构简单,扩展比较容易,而且代码也容易阅读。状态模式则通常比较复杂,因为它要从两个角色看到一个对象状态和行为的改变,也就是说它封装的是变化,要知道变化是无穷尽的,因此相对来说状态模式通常都比较复杂,涉及面很多,虽然也很容易扩展,但是一般不会进行大规模的扩张和修正

(五)观察者模式


又名 发布-订阅模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,让它们能够自动更新自己。

场景
将一个系统分割成一系列互相协作的类,有一个缺点:需要维护相关对象间的一致性。紧密的耦合会给维护和扩展带来不便。观察者模式就是为了解耦而诞生的,让原本一个对象依赖另一个对象的关系,变成了两方都依赖于抽象,而不再依赖于具体,从而使得各自的变化都不会影响另一边的变化。

学生提出问题,老师能监听到问题

优点
解耦。

缺点
如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。

(六)备忘录模式


保存一个对象的某个状态,以便在适当的时候恢复对象

比如游戏存档,或者浏览器的后退,或者编译器的撤销功能

底层用了栈的数据结构实现的

业务场景
保存及恢复数据相关的业务场景

优点:
为用户提供一种可恢复的机制
存档信息的封装

缺点:
暂存的资源多的话会销毁内存太多.

应用

1.java.util.Date

2.java.io.Serializable


后悔药人人都想要,但是事实却是残酷的,根本就没有后悔药可买,但是也不仅如此,在软件的世界里就有后悔药!备忘录模式就是一种后悔药,它给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。
所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它实现了对信息的封装,使得客户不需要关心状态保存的细节。保存就要消耗资源,所以备忘录模式的缺点就在于消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

备忘录模式包含如下角色:
Originator: 原发器
Memento: 备忘录
Caretaker: 负责人

(七)中介者模式

定义一个 封装一组对象如何交互的对象.
通过使对象明确地相互引用来促进松散耦合,并允许独立地改变他们的交互
使用场景
系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解.

比如聊天室, 比如大家都在聊天室里面,我说的话想要传达给每个人的话,如果没有这个聊天室,我要挨个说一遍,我们直接进行交互,如果有中介者的话,就经由中介者传达出来.
优点:
将一对多转化成了一对一,降低程序的复杂度.
类之间的解耦(比如我之和聊天室进行交互,就通过聊天室传达给聊天室里面的每一个人)
缺点:
中介者过多,导致系统的复杂
应用:
1.java.util.Timer
2.java.util.concurrent.Executor#execute()
3.java.util.concurrent.ExecutorService#submit()
4. java.lang.reflect.Method#invoke()

(八)迭代器模式

迭代器设计模式几乎不用我们自己手动去写,因为jdk有现成的迭代器,直接用现成的就行
迭代器模式(Iterator) 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

场景:
当需要对聚集(数据结构)有多种方式遍历时,可以考虑使用迭代器。

优点:
迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明的访问集合内部的数据。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

应用:collection容器使用了迭代器模式


对于迭代在编程过程中我们经常用到,能够游走于聚合内的每一个元素,同时还可以提供多种不同的遍历方式,这就是迭代器模式的设计动机。在我们实际的开发过程中,我们可能会需要根据不同的需求以不同的方式来遍历整个对象,但是我们又不希望在聚合对象的抽象接口中充斥着各种不同的遍历操作,于是我们就希望有某个东西能够以多种不同的方式来遍历一个聚合对象,这时迭代器模式出现了。
何为迭代器模式?所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。迭代器模式是将迭代元素的责任交给迭代器,而不是聚合对象,我们甚至在不需要知道该聚合对象的内部结构就可以实现该聚合对象的迭代。
通过迭代器模式,使得聚合对象的结构更加简单,它不需要关注它元素的遍历,只需要专注它应该专注的事情,这样就更加符合单一职责原则了。

迭代器模式包含如下角色:
Iterator: 抽象迭代器
ConcreteIterator: 具体迭代器
Aggregate: 抽象聚合类
ConcreteAggregate: 具体聚合类

(九)解释器模式


解释器模式用的比较少,一般是用第三方包来使用
使用频率非常少

原则:依赖倒转原则

场景:如果一种特定类型问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语句中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。当一个语言需要执行,并且你可将该语言中的句子表示为一个抽象语法树时,可以用解释器模式。

优点:解释器很容易改变和扩展文法,因为该模式使用类来表示文法规则,可以使用继承来改变或扩展文法,也比较容易实现文法。因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。

缺点:解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护,建议当文法非常复杂时,使用其他技术(语法分析程序、编译器生成器)。

应用:

1.java.util.Pattern

2.java.text.Normalizer

3.java.text.Format

4.javax.el.ELResolver

所谓解释器模式就是定义语言的文法,并且建立一个解释器来解释该语言中的句子。解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中。它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。

解释器模式包含如下角色:
AbstractExpression: 抽象表达式
TerminalExpression: 终结符表达式
NonterminalExpression: 非终结符表达式
Context: 环境类
Client: 客户类

(十)命令模式


将一个请求封装为一个对象,以便使用不同的请求
命令模式解决了应用程序中对象的职责以及它们之间的通信方式.命令模式可以使发送者和接收者完全解耦.发送者和接收者之间并没有直接的引用关系.
比如说老板下命令完成一个事情,他并不需要知道完成这个事情的细节.

原则:敏捷开发原则

场景:
请求的调用者和请求的接收者需要解耦,使得调用者和接收者不直接交互.

优点:
降低耦合(请求调用者和请求接受者解耦)

容易扩展新命令或者一组命令

缺点:
无限扩展会增加类的数量,提高系统的复杂度.

(十一)责任链模式


参考:
https://www.yuque.com/docs/share/e771d81d-d8b4-4f59-931a-f3fe3692a651?#

案例:ZJJ_JavaBasic_2019/11/29_17:23:27_cf5sx


为请求创建一个接收此次请求对象的链,链条中每一个元素也就是每个对象,说白了就是一个链子,链子上面有每一个节点都会处理你这个请求.比如Netty的ChannelHandler就是处理责任链,用于处理ChannelPipeline里面的每一个Channel.

场景:
一个请求的处理需要多个对象当中的一个或几个协作处理

优点
请求的发送者和请求的接收者解耦
责任链可以动态组合

缺点
责任链太长或者处理时间过长,影响性能
责任链有可能过多