(一)适配器模式


已经存在的类,但是它却不符合现有的接口规范,它的方法和需求不匹配时(方法结果相同或类似)
适配器为了复用一些现有的类,系统的数据和行为都正确,但是接口不符合,这个时候就可以采取适配器模式使得原有对象和新接口匹配.

适配器模式不是软件设计考虑的设计模式,而是随着软件维护,由于不同产品,不同厂家造成功能类似而接口不相同情况下的解决方案.
如果在做新的项目新的系统,在设计的时候不要去考虑适配器模式,适配器有点亡羊补牢的感觉.

适用场景

当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式;在系统中接入第三方组件的时候经常被使用到;

比如有很多支付系统需要对接,比如支付宝,网银的等等,我希望提供一个接口,对我内部提供统一的接口来进行支付的调用.这个场景就非常适合使用适配器模式.


优点
能够复用现存的类,但是不需要改变。
目标类和适配器类解耦,提高程序扩展性
符合开闭原则,具体的实现都在适配器中,客户端知道的只有适配器类,扩展的话只是用扩展适配器类就行了,而原有的类是不需要变化的
缺点
适配器模式有点儿“亡羊补牢”的感觉,设计阶段要避免使用。
增加系统代码可读难度,比如我调用了第一个接口,其实在适配器的内部是调用的第二个接口,这种情况在读代码的时候就很容易蒙圈


1、类适配器模式:
原理:通过继承来实现适配器功能。

当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后再继承接口B的实现类BB,这样我们可以在适配器P中访问接口B的方法了,这时我们在适配器P中的接口A方法中直接引用BB中的合适方法,这样就完成了一个简单的类适配器。
2、对象适配器模式
原理:通过组合来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后在适配器P中定义私有变量C(对象)(B接口指向变量名),再定义一个带参数的构造器用来为对象C赋值,再在A接口的方法实现中使用对象C调用其来源于B接口的方法。

3.接口适配器使用场景:
(1)想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。

参考:
https://blog.csdn.net/hujun_123456/article/details/80591021
https://blog.csdn.net/qq_15071263/article/details/74157397

1.适配器角色


● Target目标角色
该角色定义把其他类转换为何种接口,也就是我们的期望接口.

● Adaptee源角色
你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。

● Adapter适配器角色
适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:把源角色转换为目标角色,怎么转换?通过继承或是类关联的方式。



(二)桥接模式



意图
让两个不同的类之间建立某种联系,两个类建立连续的方式有很多,比如继承,组合
比如银行类和账号类建立关联


如果说某个系统能够从多个角度来进行分类(比如说银行和账号),且每一种分类都可能会变化(银行有工商银行农业银行,账号有定期和活期等等),那么我们需要做的就是讲这多个角度分离出来,使得他们能独立变化,减少他们之间的耦合,这个分离过程就使用了桥接模式。所谓桥接模式就是讲抽象部分和实现部分隔离开来,使得他们能够独立变化。

将抽象部分与它的具体的实现部分分离,使它们都可以独立地变化(在一定程度上实现了解耦),通过组合的方式建立两个类之间的联系,而不是使用继承,桥接模式可以防止子类数量过多,类爆炸情况.

桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量,核心是抽象类使用别的接口,通过组合方式使得两个不同的类建立了关联.



原则
合成/聚合复用原则

场景
1. 抽象和具体实现之间增加更多的灵活性
2. 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度的需要独立进行扩展
3. 不希望使用继承,或因为多层继承导致系统类的个数剧增.

比如说有银行 农业银行和工厂银行 有账号 活期账号和定期账号 , 银行和账号是两个模块儿的,都可以独立发展,我们可以用桥接模式让两个独立发展的模块儿发生关联.

优点:
分离抽象和实现部分
减少各部分的耦合.
符合开闭原则和合成复用原则

缺点:
1、桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
2、桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性


(三)组合模式


将对象组合成树形结构以表示“部分-整体”的层次结构。
处理一个树形结构
组合模式的优点:
组合模式让客户可以一致的使用组合结构和单个对象。

● 高层模块调用简单
一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
● 节点自由增加
使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点是不是都很容易,只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。
1.组合模式让客户可以一致的使用组合结构和单个对象。
2.清楚地定义分层次的复杂对象,表示对象的全部或部分层次,
3.让客户端忽略了层次的差异,方便对整个层次结构进行控制
4.简化客户端代码
5.符合开闭原则


组合模式的缺点:
使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。
组合模式有一个非常明显的缺点,看到我们在场景类中的定义,提到树叶和树枝使用时的定义了吗?直接使用了实现类!这在面向接口编程上是很不恰当的,与依赖倒置原则冲突,读者在使用的时候要考虑清楚,它限制了你接口的影响范围。
1.限制类型时候会较为复杂,
2.使设计变得更加抽象

使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。

场景

处理一个树形结构
这种组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理,文件目录显示,多级目录呈现等树形结构数据的操作。
从一个整体中能够独立出部分模块或功能的场景。
只要是树形结构,就要考虑使用组合模式,这个一定要记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。

(四)装饰模式


案例:ZJJ_JavaBasic_2019/12/01_11:54:26_455p8


装饰者设计模式 主要的目标是对同一个对象进行修改,进行反复的装饰封装.

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象,提供了比继承更有弹性的替代方案(扩展原有对象功能,注意是原有对象).

我们可以通过继承和组合的方式来给一个对象添加行为,虽然使用继承能够很好拥有父类的行为,但是它存在几个缺陷:
一、对象之间的关系复杂的话,系统变得复杂不利于维护。
二、容易产生“类爆炸”现象。
三、是静态的。在这里我们可以通过使用装饰者模式来解决这个问题。

优点
1. 在不改变原有对象的情况下给一个对象扩展功能,就增加对象功能来说,装饰模式比生成子类实现更为灵活。
2. 可以把类的核心职责和装饰功能区分开来,结构清晰,明了并且可以去除相关类的重复的装饰逻辑。
3. 通过使用不同的装饰类以及这些装饰类的排列组合,可以实现不同效果.
4. 符合开闭原则

缺点
1. 会出现更多的类,更多的代码,增加程序的复杂性
2. 动态装饰时,多层装饰时会更复杂.

装饰模式和代理模式
装饰模式关注在一个对象上动态的添加方法,代理模式关注于控制对对象的访问.
代理模式中的代理类可以对它的客户隐藏一个对象的具体信息,通常使用代理模式的时候常常在一个代理类中创建一个对象的实例,而使用装饰者模式的时候,通常会把原始对象作为一个参数传给装饰者的

使用场景
1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2. 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。

装饰模式是为了已有功能动态地添加更多功能的一种方式,当系统需要新功能的时候,是向旧类中添加新的代码,这些新的代码通常装饰了原有类的核心职责或主要行为。装饰着模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择的、按顺序地使用装饰功能包装对象。


应用:Java I/O使用装饰模式设计,JDK中还有很多类是使用装饰模式设计的,如:Reader类、Writer类、OutputStream类等。


应用实例

就是最常见的装饰者模式了,通过BufferedReader对已有对象FileReader的功能进行加强和优化。其实它不仅可以加强FileReader,所有的字符输入流都可以通过这种方式进行包装。它是如何实现的呢?注意重点来了:其实很简单,它只不过将所有的字符输入流抽象出了一个基类或接口 即Reader,然后通过构造方法的形式将Reader传递给BufferedReader,此时BufferedReader就可以对所有的字符输入流进行拦截和优化了。

试想一下,如果采用继承机制,每个XXXReader就要衍生出一个BufferedXXXReader,再加上字符输出流和字节输入输出流,那么Java的IO体系结构该是多么的臃肿不堪啊!而装饰者模式的出现解决了这个问题,并且,装饰者的出现也再一次的证明了面向对象的设计原则:多用组合,少用继承!对扩展开放,对修改关闭!



(五)外观模式


迪米特法则

我们都知道类与类之间的耦合越低,那么可复用性就越好,如果两个类不必彼此通信,那么就不要让这两个类发生直接的相互关系,如果需要调用里面的方法,可以通过第三者来转发调用。外观模式非常好的诠释了这段话。外观模式提供了一个统一的接口,用来访问子系统中的一群接口。它让一个应用程序中子系统间的相互依赖关系减少到了最少,它给子系统提供了一个简单、单一的屏障,客户通过这个屏障来与子系统进行通信。通过使用外观模式,使得客户对子系统的引用变得简单了,实现了客户与子系统之间的松耦合。但是它违背了“开闭原则”,因为增加新的子系统可能需要修改外观类或客户端的源代码。

外观模式(Facade),他隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。这种类型的设计模式属于结构性模式。为子系统中的一组接口提供了一个统一的访问接口,这个接口使得子系统更容易被访问或者使用。

结构型模式 - 图1

使用场景
1- 为复杂的模块或子系统提供外界访问的模块;
2- 子系统相互独立;
3- 在层析结构中,可以使用外观模式定义系统的每一层的入口。

比如我在淘宝兑换礼物,我只需要跟淘宝交互,至于后面的积分扣除,物流发礼物等等我不需要去关注,这就是对后面复杂的模块儿提供了简单的访问.

第二个例子:领导让你完成一个excel表格导入日志数据的功能。那你需要写一个读取excel表格数据的工具类。然后再写一个从读取到的数据筛选出符合格式的日志数据的方法,然后再写一个日志入库的方法。这几个方法都是不同的作用,只为了完成导入的功能。而你的领导不管你是怎么实现的,反正给你一个excel表格。你就把数据导入就好了。你和领导交互,但是领导不需要知道具体实现,而这个就是外观模式。

优点
简化了调用过程,无需了解深入子系统,防止带来风险.
减少系统以来,松散耦合, 客户端不和子系统直接交流而是和外观对象交流.
完美的体现了依赖倒转原则和迪米特法则。客户端只和外观类打交道.
缺点
增加子系统,扩展子系统行为容易引入风险.不符合开闭原则

外观模式和中介者模式
外观模式关注的是外界和子系统之间的交互
中介者模式关注的是子系统内部之间的交互
外观模式和单例模式
通常可以给外观模式的外观对象做成单例模式来结合使用.
外观模式和抽象工厂模式
外观类可以通过抽象工厂获取子系统实例,子系统可以将内部对外观类进行屏蔽.

(六)亨元模式


一句话概况:减少创建对象的数量,从而减少内存的占用,从而提高性能.

提供了减少对象数量从而改善应用所需的对象结构
运用共享技术有效的支持大量细粒度的对象。

享元模式使用的场景
1. 在系统中,如果有大量的对象时,就有可能造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求我们就返回在内存中的已有对象,避免重新创建.
2. 系统中有大量相似对象,需要缓冲池的场景.

应用

String类型就是如果有就返回,如果没有就创建一个字符串并且保存在字符串的缓存池里面.
数据库的连接池,里面都是创建好的连接对象,需要的时候就拿来用,不需要的时候就放回去.

优点
减少对象创建的创建,降低内存中对象的数量,降低系统的内存,提高效率.
减少内存之外的其他资源占用(比如创建对象需要时间,通过享元模式可以减少new对象的次数,也就节省了时间.)

缺点:
1. 关注内/外部状态
2. 使系统,程序的逻辑复杂化
关注线程安全问题

内部状态和外部状态
内部状态是指享元对象内部并且不会随着环境改变而改变的共享部分.
大白话,内部状态是享元对象的一个属性,属性不会随着外部变化而变化

外部状态是随着环境的改变而改变,这种状态是不可以共享的状态,
我们在调用享元模式获取享元对象的时候我们通过方法的参数传过来一个状态,列如说int 0 是什么样子的 1又是什么样子的, 这就是大白话来说的了

享元模式和单例模式
享元模式的目的是共享,避免多次创建耗费资源,单例模式的目的是限制创建多个对象以避免冲突等,所以即使都是一个对象,目的也不同

(七)代理模式

在不改变原有方法的情况下对原有的方法进行功能的扩展,
场景:
比如增删改查之后我们添加日志记录功能,或者增删改查之前我们添加权限校验功能

AOP(Aspect-OrientedProgramming,面向切面编程),AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。
优点
1)代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度。
2)代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用。代理对象也可以对目标对象调用之前进行其他操作。

缺点:
1)在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
2)增加了系统的复杂度。
详细介绍

代理模式就是动态代理,代理模式使用到极致开发就是AOP
代理模式提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子

用图表示如下:
结构型模式 - 图2
代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象


1.装饰模式和代理模式


装饰模式关注在一个对象上动态的添加方法,代理模式关注于控制对对象的访问.
代理模式中的代理类可以对它的客户隐藏一个对象的具体信息,通常使用代理模式的时候常常在一个代理类中创建一个对象的实例,而使用装饰者模式的时候,通常会把原始对象作为一个参数传给装饰者的

2.三种代理模式比较

静态代理

具体代码去看zjj_parent

1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:

因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.





动态代理


动态代理分为两种,一种是JDK反射机制提供的代理,另一种是CGLIB代理。在JDK代理,必须提供接口,而CGLIB则不需要提供接口,在Mybatis里两种动态代理技术都已经使用了,在Mybatis中通常在延迟加载的时候才会用到CGLIB动态代理。

cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。


JDK动态代理

底层通过反射实现

在 Java 的动态代理中,主要涉及2个类,java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler我们需要一个实现 InvocationHandler 接口的中间类,这个接口只有一个方法 invoke 方法,方法的每个参数的注释如下代码。我们对处理类中的所有方法的调用都会变成对 invoke 方法的调用,这样我们可以在 invoke 方法中添加统一的处理逻辑(也可以根据 method 参数判断是哪个方法)。中间类(实现了 InvocationHandler 的类)有一个委托类对象引用,在Invoke方法中调用了委托类对象的相应方法,通过这种聚合的方式持有委托类对象引用,把外部对 invoke 的调用最终都转为对委托类对象的调用。实际上,中间类与委托类构成了静态代理关系,在这个关系中,中间类是代理类,委托类是委托类。然后代理类与中间类也构成一个静态代理关系,在这个关系中,中间类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。

java 动态代理最大的特点就是动态生成的代理类和委托类实现同一个接口。java 动态代理其实内部是通过反射机制实现的,也就是已知的一个对象,在运行的时候动态调用它的方法,并且调用的时候还可以加一些自己的逻辑在里面。


1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
3、Interface:对于JDK Proxy,业务类是需要一个Interface的,这是一个缺陷(必须要有接口,否则无法使用);
4、Proxy:Proxy类是动态产生的,这个类在调用Proxy.newProxyInstance()方法之后,产生一个Proxy类的实力。实际上,这个Proxy类也是存在的,不仅仅是类的实例,这个Proxy类可以保存在硬盘上;
5、Method:对于业务委托类的每个方法,现在Proxy类里面都不用静态显示出来。
6、InvocationHandler:这个类在业务委托类执行时,会先调用invoke方法。invoke方法在执行想要的代理操作,可以实现对业务方法的再包装,invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。


源码分析
https://www.yuque.com/docs/share/3f8c34dd-a2b6-49c3-a2e7-76d47d3377ac?#


cglib动态代理
底层通过继承实现

JDK 动态代理依赖接口实现,而当我们只有类没有接口的时候就需要使用另一种动态代理技术 CGLIB 动态代理.

原理:对指定的目标生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

CGLIB 代理是针对类来实现代理的,原理是对指定的委托类生成一个子类并重写其中业务方法来实现代理。代理类对象是由 Enhancer 类创建的。CGLIB 创建动态代理类的模式是:

1. 查找目标类上的所有非final的public类型的方法(final的不能被重写)
2. 将这些方法的定义转成字节码
3. 将组成的字节码转换成相应的代理的Class对象然后通过反射获得代理类的实例对象
4. 实现 MethodInterceptor 接口,用来处理对代理类上所有方法的请求

对于需要被代理的类,它只是动态生成一个子类以覆盖非final的方法,同时绑定钩子回调自定义的拦截器。值得说的是,它比JDK动态代理还要快。值得注意的是,我们传入目标类作为代理的父类。不同于JDK动态代理,我们不能使用目标对象来创建代理。目标对象只能被 CGLIB 创建。在例子中,默认的无参构造方法被使用来创建目标对象。

1、CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
2、 用CGlib生成代理类是目标类的子类。
3、 用CGlib生成 代理类不需要接口
4、 用CGLib生成的代理类重写了父类的各个方法。
5、 拦截器中的intercept方法内容正好就是代理类中的方法体


源码分析
https://www.yuque.com/docs/share/14f085a8-e090-4c2e-9db5-0d2261d9fd3c?#



两者比较

注意:jdk的动态代理只可以为接口去完成操作,而cglib它可以为没有实现接口的类去做代理,也可以为实现接口的类去做代理。

spring两种代理方式
1. 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口
2. 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。



动态代理原理


结构型模式 - 图3

原理
invoke方法有三个参数:

1. Object proxy :生成的代理对象
2. Method method:目标对象的方法,通过反射调用
3. Object[] args:目标对象方法的参数

| public class Advice implements InvocationHandler {

People people;

public Advice(People people_) {
_this
.people = people;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args_) _throws Throwable {

//前置增强 before();

//被代理方法 Object value = method.invoke_(_people, args);

//后置增强 after();

return value;
_}

_private void
before() {
System.out.println(“===========jack吃饭之前需要洗手==========”);
_}

_private void
after() {
System.out.println(“===========jack吃完饭要洗碗=============”);
}
}

测试类
People proxyObject = (People) Proxy.newProxyInstance(MyTest.class.getClassLoader(), new Class<?>[]{People.class},
new Advice_(_new Jack()));

proxyObject.eat(“chi”); | | —- |




Proxy.newProxyInstance 方法
1. 通过字符串的方式拼凑一个类,然后再给这个类写到Java文件里面,然后编译这个文件,然后再把这个字节码文件通过类加载器加载到jvm内存就搞定了.
2. 在运行的时候创建了一个$Proxy0的对象,在这个对象里面会实现一个People接口,在内存里面就会有一个eat方法.
3. 然后通过调用代理对象的eat方法,然后在内存里面的eat方法就会执行 invoke方法.就会调用到Advice类(实现了InvocationHandler接口),最终就会调用到invoke方法.