导图社区 设计模式
这是一篇关于设计模式的思维导图,主要内容包括:适配器模式,委派模式,策略模式,模板模式,原型模式(创建型模式),设计模式分类,代理模式为其他对象提供一种代理,用以控制对这个对象的访问,单例模式,工厂模式,设计原则。
编辑于2025-02-23 19:08:11设计模式
设计原则
SOLID
单一职责原则 Single ResponsibilityPrinciple
一个类应该只有一个发生变化的原因,否则类应该被 拆分
开闭原则
对扩展开放、对修改关 闭
里氏替换原则
子类可以扩展父类的功能,但不能去 改变父类原有的功能
接口隔离原则
接口拆分得更小,更专用
依赖倒置原则
设计代码结构时,高层模块不应依赖低 层模块,两者都应该依赖抽象,抽象不应依赖细节,细节应 该依赖抽象。
KISS 原则
保持它的简单和愚蠢
YAGNI 原则
不要去做一些过度设计
DRY 原则
不要写重复的代码
迪米特法则
核心主旨就是一个对象应该对其他对象保持最少的了解,为了减少类和类之间的耦合,每个类越独立越好。
设计模式分类
创建型:处理对象的创建过程,5种
工厂方法模式
抽象工厂模式
单例模式
建造者模式
原型模式
结构型:处理类或者对象的组合,7种
适配器模式
装饰器模式
代理模式
外观模式
桥接模式
组合模 式
享元模式
行为型:对类或对象怎样交互和怎样分配职责进行描述,11种
策略模式
模板方法模式
观察者模式
迭代器模式
责任链模式
命令模式
备忘录模式
状态模式
访问者模式
中介者模式
解释器模式
工厂模式
简单工厂模式
工厂方法模式
抽象工厂模式
单例模式
饿汉式:加载的时候就创建对象初始化
懒汉式:调用的时候才创建对象实例
静态内部类:在内部类里面去创建对象实例
枚举单例:线程安全的,只会装载一次,枚举类是所有单例类 实 现中唯一不会被破坏的单例模式(解决了反射与序列化破 坏)
反射破坏单例:虽然构造器设置为私有,但是可以通过设置强制访问 来调用其构造函数,解决办法:在私有构造函数中抛出异常,禁止反射创建。
序列化破坏单例:反序列化后的对象会重新分配内存,即重新创建.解决方法:添加 readResolve 方法
容器式单例
代理模式为其他对象提供一种代理,用以控制对这个对象的访问
静态代理分析:
不修改原始服务类的情况下,添加一些额外的功能
缺点: 1. 需要增加类的数量 2.不够用灵活 3.维护成本高
动态代理分析
实现方式
基于 JDK 的动态代理
大致流程就是:
1. 创建 JDK 动态代理对象,并将 MemberServiceImpl 对象作为 target 参数传进去。
2. 接着调用 getProxy 方法,其中调用 Proxy.newProxyInstance 来加载这个 MemberServiceImpl 的代理对象。
3. 通过生成之后的代理对象调用 login 方法,那就不是 MemberServiceImpl 本身的这个方法了,而是调到了InvocationHandler里的 invoke 方法了。
4. invoke方法中就可以做对应的增强,其中 method 参数就是被代理对象的方法,args 就是被代理对象的方法
源码JDK动态代理分析
newProxyInstance 分析
(初步校验)在创建代理前,首先会先做一些基础的校验,如空参校验,安全检查。
1. 代理对象生成)此处 getProxyClass0 是一段很重要的逻辑,它会去生成代理对象的字节码,并将代理对象加载到JVM内存中。
2. 进入 getProxyClass0 方法分析一下,这个逻辑首先也是做了一段校验,接着就从代理类缓存中试着去获取代理类,如果存在则直接返回,这一步就是为了提高获取代理类的效率,不要每次都去生成。如果不存在则生成代理类,并将其加入缓存中。
3. 接着走进 proxyClassCache.get(loader, interfaces) 方法来分析一下。这一步主要是做了一些基础校验,然后需要从缓存 map 中拿到对应的 value,也就是 valuesMap 这个参数。如果不存在,我们需要给他做一下初始化,初始化成一个空的 ConcurrentHashMap。
3. 接着代码往下走,这一步便是从 map 的第二层级 ConcurrentMap<Object, Supplier<V>> 中获取到对应的 Supplier,如果 Supplier 不存在,则需要创建一个 Factory 赋值给 Supplier,如果存在则直接获取就完事了。接着,就需要通过 supplier 的 get() 方法来将代理类对应的字节码创建出来,并将字节码加载到JVM内存中,这一个方法便是这里的核心逻辑。
4. 然后再来看一下这个 supplier 里的 get() 方法是怎么调用的。此处跳转到 Factory 里的 get() 方法。这段代码的逻辑,主要也就是创建 value,也就是创建代理对象,只不过这里还需要保证线程安全,比如说缓存中的 supplier 被修改了就不能再往下执行了。
5. 再然后看下真正创建代理对象的地方 valueFactory.apply(key, parameter),它是通过 valueFactory 来做的,这个 valueFactory 就是 ProxyClassFactory,代理类工厂,顾名思义,这个工厂就是用来生产代理类的。首先,这一段逻辑为非核心逻辑,大致上就是验证接口数组的有效性,确保生成的代理类中实现的接口是有效的
6. 接下来的逻辑,就是确定要生成的代理类的包名和类名。逻辑是:如果传入的接口中有非公共接口,那么生成的代理类将位于这些非公共接口所在的包中。如果所有的接口都是公共接口,那么代理类将位于默认包 com.sun.proxy 中。默认代理类不会生成到文件系统中,可以在项目启动时在 VM 参数中配置参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 则可将代理类的字节码生成到文件系统下。
7. 接下来就是最核心最核心的逻辑了,生成字节码,还有加载到内存,以便后续可以通过 Class 对象使用该代理类。通过 JDK动态代理 生成代理类的过程便是如此了。当此处执行完成之后,Class<?> cl = getProxyClass0(loader, intfs); 这段逻辑就能把创建好的代理类返回了。
8. 接下来便是最后一步了,这一步会将代理类创建成一个代理对象。该代理对象实现了指定的接口,并且会将方法调用委托给指定的 InvocationHandler。
基于 CGLIB 的动态代理。
大致流程
1. 有一个名为 LoginService 的普通类,它包含一个 login 方法,用于执行登录操作,当调用这个方法时,它会在控制台上输出一行文本。
2. 有一个名为 CglibDynamicProxy 的类,它实现了 MethodInterceptor 接口。这个类的目的是创建一个CGLIB动态代理对象,用于代替其他对象执行方法。
3. 在 CglibDynamicProxy 类的构造函数中,它接收一个目标对象 target,表示你希望代理的对象。
4. getProxy 方法用于创建代理对象。它使用CGLIB的 Enhancer 类,将目标对象的类作为超类(父类),并设置当前对象作为方法拦截器(this 表示当前对象)。然后,它使用 Enhancer 创建代理对象并返回。
5. 在 intercept 方法中,当代理对象的方法被调用时,它会首先输出一行文本表示CGLIB动态代理的开始,然后调用目标对象的相应方法。接着,它再次输出一行文本表示CGLIB动态代理的结束。
6. 在 CglibDynamicTest 类中,我们创建了一个 CglibDynamicProxy 对象,并将 LoginService 对象传递给它作为目标。然后,我们使用 getProxy 方法获取代理对象,并将其转型为 LoginService 类型。
7. 最后,我们使用代理对象来调用 login 方法。每次方法被调用时,代理会在方法前后输出文本,以表示CGLIB动态代理的开始和结束。
两种动态代理的区别
1. 实现方式不同
JDK动态代理通过反射去实现被代理对象的所有接口,所以JDK只能代理实现了接口的对象;
CGLlib会对代理类所有方法进行重写,所以不是所有的方法都能被代理(private、final不行);
2. 拦截被代理对象的方式不同
JDK动态代理是通过实现InvocationHandler接口来实现的,代理对象调用任意方法时,都会先进入 InvocationHandler的invoke()方法,由该方法决定是否调用原对象的方法;
CGLIB动态代理则是通过生成目标类的子类,覆盖其中的业务方法,在其内部加入横切逻辑实现拦截的;
3. 性能和效率不同
JDK 动态代理跟CGLib代理都是在运行期生成新的字节码,但是 CGLib 实现更为复杂,用的是 ASM 框架,生 成代理类的效率比 JDK 低;
JDK 动态代理是通过反射机制去查询所有被代理对象的接口,CGLib 代理是通过 FastClass机制直接调用方法,所以执行效率,CGLib 比 JDK 代理要高;
代理模式优缺点
优点
能将代理对象与真实被调用的目标对象分离,降低系统的耦合程度,易于扩展,在增强目标对象职责的同时,可以起到保护对象的作用
缺点
增加系统的复杂度,会在客户端与目标对象之间增加一个代理对象,请求速度变慢
缺点
态代理是基于反射实现的,在运行的时候要执行反射的逻辑,性能还是有一定损耗的,但是影响不大
原型模式(创建型模式)
核心思想:基于一个已有的 对象复制一个对象出来,通过复制来减少对象的直接创建的 成本。
深拷贝
实现方式两种
一种是通过递归拷贝对象
第二种是通过对象序列化来做,
浅拷贝:只会拷贝地址:两个对象都是依赖同一个对象
总结:
浅拷贝只会复制对象里面的基本数据类型和引用对象的内存地址,不会递归地复制 引用对象,以及引用对象的引用对象
而深拷贝就是会完全拷贝一个新的对象出来。所以,深拷贝的操作会比浅拷贝更 加耗时,性能更差一点。同时浅拷贝的代码写起来也比深拷贝的代码写起来更简单一点。
如果咱们拷贝的对象是不可变的,也就是说我这个对象只会 查看,不会增删改,那么,直接用浅拷贝就好了,如果拷贝 的对象存在增删改的情况,那么就需要使用深拷贝了。
总而言之,就是要深浅得当,根据项目实际情况而定。
模板模式
在父类中定义业务处理流程的框架,到子类中去做具体的实现
策略模式
定义一个主要的接口,然后很多个类去实现这个接口
委派模式
是一种面向对 象的设计模式,基本作用就是负责任务的调用和分配; 是一种特殊的静态代理,可以理解为全权代理(代理模式注 重过程,而委派模式注重结果),委派模式属于行为型模 式,不属于GOF23种设计模式;
适配器模式
核心思想就是是:将一个类的接口 转换成客户希望的另一个接口,讲白了就是通过适配器可以 将原本不匹配、不兼容的接口或类结合起来