设计模式总结
2022-09-25 16:39:12 0 举报
AI智能生成
设计模式总结
作者其他创作
大纲/内容
委派模式
概念
委派模式基本作用是任务的调度和任务分配,将任务的分配和执行分离开。不属于GOF23种设计模式
应用场景
委派对象本身不知道如何处理一个请求,把请求交给其他对象处理
实现程序的解耦
ClassLoader双亲委派机制
反射Method类委托MethodAccessor类的invoke0方法调用方法
BeanDefinitionParserDelegate,根据不同类型委派不同的逻辑解析BeanDefinition
与代理模式的区别
委派模式是行为型模式,代理模式是结构型模式
委派模式是任务派遣,注重结果;代理模式是代码增强,注重过程。
委派模式是一种特殊的静态代理,相当于全权代理
优点
将大型任务细化,统一管理子任务完成情况实现任务跟进,加快任务执行的效率。
缺点
任务委派方式需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下中能需要进行多重委派,容易造成紊乱。
策略模式
概念
对算法家族进行封装,算法间可以互相替换,不会影响使用算法的用户
适用场景
有很多类,而这些类的区别仅仅在于行为不同
一个系统需要动态选择其中一种算法
应用场景
DispatcherServlet
Arrays类的parallelSort方法使用Comparator类实现策略模式
Spring的Resource接口,不同类型的资源用不同的获取方式
Spring的InstantiationStartegy接口
优点
符合开闭原则
避免使用过多if else、switch
提高算法的保密性和安全性
缺点
客户必须先知道有哪些策略,并且自己决定选哪个策略
代码中会产生很多策略类,增加维护难度
模板方法模式
概念
定义一个算法骨架,并允许子类为其中的一个或者多个步骤提供实现
使子类在不改变算法结构的情况下,重新定义算法的某些步骤
实际应用
JdbcTemplate类中对RowMapper类的使用
AbstractList的get方法
HttpServlet的doGet和doPost方法
MyBatis的BaseExecutor类
优点
相同处理逻辑放到抽象父类中,提高代码的可复用性。
不同的代码放在不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。
把不变的行为写在父类上,提供代码复用的平台,符合开闭原则
缺点
每一个抽象类都需要子类来实现,导致类个数增加
类个数增加,间接的增加系统实现的复杂度
继承关系的缺点它都有,如父类添加新的抽象方法,所有子类都得实现这个新增的抽象方法。
代理模式
概念
指为其他对象提供一种代理,以控制对这个对象的访问,而代理对象在可以在客户端和目标对象之间起到中介的作用!!!也可以理解为代码增强,在原代码逻辑前后增加一些代码逻辑,而使调用者无感知
静态代理
静态代理手动实现代理,代理类需要与被代理类实现相同的接口,不易维护,一旦接口增加方法,目标对象和代理对象都需要修改。违背开闭原则。
动态代理
JDK动态代理
概念
采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的
实现原理
1.获取被代理对象的引用,并且反射获取它所有的接口
2.jdk动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口
3.动态生成java代码,根据新加的业务方法生成
4.编译新生成的class文件,重新运行
重点
JDK代理的目标对象必须要实现接口,因为JDK代理是根据被代理对象的所有接口去生成新的方法!但是Proxy.newProxyInstance返回的是接口类,而非实现,所以JDK只能代理接口!!
CGLB动态代理
概念
CGLib代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现动态代理的
实现原理
1. 创建被代理的类及方法;
2. 创建一个实现接口 MethodInterceptor 的代理类,重写 intercept 方法;
3. 创建获取被代理类的方法 getInstance(Object target);
4. 获取代理类,调用代理类方法。
重点
CGLib代理不需要被代理对象实现任何接口,它会重写被代理类的所有方法,所以不能代理final修饰
的方法或者类,因为被final修饰后,表明这个类不能被继承
的方法或者类,因为被final修饰后,表明这个类不能被继承
静态代理与动态代理的区别
1. 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则
2. 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
JDK动态代理跟CGLib动态代理的区别
1. JDK动态代理通过反射去实现被代理对象的所有接口,所以JDK只能代理实现了接口的对象,CGLlib对代理类所有方法重写
2. JDK动态代理跟CGLib代理都是在运行期生成新的字节码,但是CGLib实现更为复杂,用的是ASM框架,生成代理类的效率比JDK低
3. JDK动态代理是通过反射机制去查询所有被代理对象的接口,CGLib代理是通过FastClass机制直接调用方法,所有执行效率,CGLib比JDK代理要高
Spring框架中的代理模式
当Bean有实现接口时,Spring会用jdk动态代理
当Bean未实现接口时,Spring会用cglib动态代理
Spring也可以通过配置强制使用cglib动态代理
优点
能将代理对象与真实被调用的目标对象分离,降低系统的耦合程度,易于扩展,在增强被代理对象职责的同时,可以起到保护被代理对象的作用
缺点
增加系统的复杂度,会在客户端与目标对象之间增加一个代理对象,请求速度变慢
装饰器模式
概念
在不改变原有对象的基础上,将功能附加到对象上,提供比继承更有弹性的替代方案(扩展原有对象的功能)
适用场景
扩展一个类的功能或者给一个类添加附加职责
动态的给一个类添加额外的功能,这些功能可以再动态的撤销
核心
装饰器模式的核心是功能扩展。使用装饰器模式可以透明且动态地扩展类的功能
实现原理
让装饰器实现被包装类(ConcreteComponent)相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有功能上添加新功能了。而且由于装饰器与被包装类属于同一类型(均为Component),且构造函数的参数为其实现接口类(Component),因此装饰器模式具备嵌套扩展功能,这样我们就能使用 琴模式一层一层的对最底层被包装类进行功能扩展了。
装饰器模式与代理模式对比
是一种特殊的代理模式
装饰器模式强调自身功能的扩展,透明的扩展,可动态定制的扩展
代理模式强调代理过程的控制
优点
对继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果
完全遵守开闭原则
缺点
会出现更多代码,更多的类,增加程序复杂性。
动态装饰时,多层装饰时会更复杂
应用场景
BufferedInputStream装饰FileInputStream
BudderedReader装饰各种reader
Spring框架的TransactionAwareCacheDecorator
七大设计原则
开闭原则
定义:开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架,用实现扩展细节。
减少对已有代码的修改(比如已有方法、逻辑),提升扩展性;可以提高软件系统的可复用性及可维护性,同时减少对已有代码的影响。
方式一:可以采用继承、重写等方式。
方式二:还可以更多的依赖接口与抽象,不要依赖具体实现!! 因为你依赖实现,改动只能改动具体实现。
依赖倒置原则
定义:依赖倒置原则(Dependence Inversion Principle,DIP)是指设计代码结构时,抽象不依赖细节,细节应该依赖抽象。
通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。
单一职责原则
定义:定单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。
举例:假设我们有一个 Class负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。这样一来,这个 Class 存在两个导致类变更的原因。如何解决这个问题呢?我们就要给两个职责分别用两个Class来实现,进行解耦。后期需求变更维护互不影响。
这样的设计可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。总体来说就是一个 Class/Interface/Method 只负责一项职责。但实际情况很多时候是不符合单一职责的,因为会加大代码量!!以及让代码过于分散!
接口隔离原则
定义:接口隔离原则(Interface Segregation Principle, ISP)是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
接口隔离原则符合我们常说的高内聚、低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。
迪米特法则
定义:迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合。
迪米特原则主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类, 而出现在方法体内部的类不属于朋友类。实体类之间尽量减少相互作用!! 简单就是一句话,减少类之间的耦合,这也是为什么我们很多方法定义成private或者protect的原因!
里式替换原则
定义:里氏替换原则(Liskov Substitution Principle,LSP)是指如果对每一个类型为T1的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序P 在所有的对象o1都替换成 o2 时,程序 P 的行为没有发生变化,那么类型T2 是类型T1 的子类型。
定义重新理解:可以理解为一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。根据这个理解,我们总结一下:引申含义:子类可以扩展父类的功能,但不能改变父类原有的功能。
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。
合成复用原则
定义:合成复用原则(Composite/Aggregate Reuse Principle,CARP)是指尽量使用对象组合(has-a)/聚合(contanis-a),而不是继承关系达到软件复用的目的。继承我们叫做白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合也称之为黑箱复用,对类以外的对象是无法获取到实现细节的。
可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
设计模式分类
创建型:创建型模式,就是指用于描述“怎样创建对象”
单例模式
工厂方法模式
抽象工厂模式
建造者模式
原型模式
结构型:用于描述如何将类或对象按某种布局组成更大的结构
门面模式
适配器模式
代理模式
桥接模式
装饰模式
组合模式
享元模式
行为型:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责
模板方法模式
策略模式
委派模式
责任链模式
迭代器
。。。
工厂模式
简单工厂模式
是指有一个工厂对象,来根据传入的是什么对象类型,决定创建出哪一种产品类的实例,但它不属于23种设计模式
组成结构:工厂(CourseFactory)、抽象类产品(ICourse)、具体产品(JavaCourse)
工厂方法模式
是指定义一个创建对象的工厂接口,但让实现这个工厂接口的不同的具体工厂类来决定实例化哪个类,工厂方法让类的实例化推迟到具体的工厂类中进行
核心思想:将对象创建的逻辑下推到子类,就是创建多个子工厂
抽象工厂模式
是指创建工厂抽象类,定义产品族,产生多个具体子工厂,每个具体工厂创建一个产品族,即多个具体产品实例。
单例模式
定义:是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
应用场景:J2EE标准中的ServletContext、ServletContextConfig等,Spring框架中的ApplicationContext、数据库的连接池等也都是单例模式。
饿汉式单例
在类加载就实例化,线程安全,执行效率比较高,但是不确定是否需要使用,造成内存浪费
实现方式
静态常量
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance() {
return instance;
}
return instance;
}
静态代码块(本质与静态常量没有区别)
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
return hungrySingleton;
}
懒汉式单例
概念:懒汉式单例模式在外部需要使用的时候才进行实例化,节省内存。线程不安全,需要通过synchronized或双重检查锁来实现线程安全。
实现方式
基础方式:有线程安全问题
private static LazySimpleSingletion instance;
private LazySimpleSingletion(){}
public static LazySimpleSingletion getInstance(){
//有线程安全问题,可以使用
if(instance == null){
instance = new LazySimpleSingletion();
}
return instance;
}
//有线程安全问题,可以使用
if(instance == null){
instance = new LazySimpleSingletion();
}
return instance;
}
方法类锁:在公有方法上加锁,性能低
private static LazySimpleSingletion instance;
private LazySimpleSingletion(){}
public synchronized static LazySimpleSingleton getInstance() {
if (instance == null) {
instance = new LazySimpleSingleton();
}
return instance;
}
if (instance == null) {
instance = new LazySimpleSingleton();
}
return instance;
}
双重检查锁:第一重判断提升性能,第二重判断保证单例
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//检查是否要阻塞,如果已经创建过,就不需要再进入加锁代码块拉低性能,大大的提升性能,如果
没有该检查,每次都会去竞争锁
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
//如果没有该判断,2个线程在第一个if都判断为null,那么就会创建2个实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
//检查是否要阻塞,如果已经创建过,就不需要再进入加锁代码块拉低性能,大大的提升性能,如果
没有该检查,每次都会去竞争锁
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
//如果没有该判断,2个线程在第一个if都判断为null,那么就会创建2个实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
为什么要加volatile?
底层cpu优化,在不影响最终结果的时候,会进行指令重排。也就是说对象的实例化会在初始化之前执行。那么当另外的线程来判断第一个instance == null 不满足条件,但是对象还是一个没有初始化的对象。所以需要加volatile来禁止指令重排。
静态内部类
概念:双重检查锁有一定性能问题,那么我们还可以采用静态内部类的方式,该方式同样也是利用了类的加载机制,它与饿汉模式不同的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。
private LazyStaticInnerClassSingleton(){
//解决反射破坏,因为反射可以调用私有的构造器
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
//解决反射破坏,因为反射可以调用私有的构造器
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
return LazyHolder.INSTANCE;
}
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
注册式单例
枚举式单例模式
枚举式单例模式,无法通过反射及反序列化来破坏单例。无法通过反射破坏单例是因为jdk底层做了限制,当发现反射调用的是枚举的构造器时,会抛出“异常”;无法反序列化来破环单例是因为反序列化时如果该Enum类已被实例化则通过类名及类对象找到该枚举类并返回,所以不会产生多实例。
缺点:枚举类一开始就会初始化好,相当于饿汉模式,内存浪费
容器式单例
第一次使用,创建对象并放到容器中,后面获取的时候在容器中拿。
方便于管理众多的单例对象,但会出现线程安全问题,也会出现反射和反序列化破坏其单例的现象,不过spring中的对象管理
通过该方式。
方便于管理众多的单例对象,但会出现线程安全问题,也会出现反射和反序列化破坏其单例的现象,不过spring中的对象管理
通过该方式。
缺点:有线程安全问题
ThreadLocal单例
优点:性能高,不存在线程安全问题,代码优雅
缺点:只能在线程中保持单例
反射破坏单例原理
虽然构造器设置为私有,但是可以通过设置强制访问来调用其构造函数,具体为: c.setAccessible(true);这样就会实例化一个新的对象
原因:反射不会调用getIstance接口
原因:反射不会调用getIstance接口
序列化破坏单例原理
反序列化后的对象会重新分配内存,即重新创建
readObject0 中为object的时候,调用readOrdinaryObject,里面如果有
hasReadResolveMethod,会调用ResolveMethod重写。其实序列化底层也是调用了newInstance
readObject0 中为object的时候,调用readOrdinaryObject,里面如果有
hasReadResolveMethod,会调用ResolveMethod重写。其实序列化底层也是调用了newInstance
readResolve()方法防止反序列化破坏单例原理
在反序列化调用readObject()方法中,会先反序列化一个实例,再进行判断是否定义了该方法,如果
定义了该方法,则将刚才反序列化生成的对象进行覆盖。其实实际上实例化了两次,只不过新创建的对象没有被返回
定义了该方法,则将刚才反序列化生成的对象进行覆盖。其实实际上实例化了两次,只不过新创建的对象没有被返回
原型模式
概念
原型实例指定创建对象的种类,并通过拷贝这些原型创建新的实例,调用者不用知道任
何细节,不用调用构造函数。
何细节,不用调用构造函数。
适用场景
初始化耗费资源多
new对象繁琐
构造方法复杂
浅克隆
实现方式
类实现Cloneable接口,调用clone方法,即可获得浅克隆对象。
优点
实现简单,速度快
缺点
仅拷贝对象本身,不拷贝对象中的引用指向的对象
单例对象再去使用浅克隆,会破坏单例
解决办法
覆盖clone方法,直接返回单例对象
单例类不使用原型方法创建
深克隆
实现方式
序列化再反序列化
对象转json,json再转回对象
自己手动重新创建浅克隆对象里引用的其他对象
优点
对象和对象中引用的对象都拷贝
缺点
实现比浅克隆复杂
性能比浅克隆慢
单例对象再去使用深克隆,会破坏单例
解决办法
单例类不使用原型方法创建
实际应用场景
ArrayList的clone方法,实现深克隆
HashMap的clone方法实现深克隆
缺点
必须使用克隆的方式
改变类的行为需要直接改代码,违反开闭原则
浅拷贝、深拷贝必须应用得当!
优点
性能优良,java自带的clone是基于内存二进制流的拷贝,比new一个对象性能强。
可以保存对象的状态,不用再重新初始化状态
建造者模式
概念
将一个复杂对象的构建过程与它的定义分离,使得同样的构建过程可以创建不同的实例,属于创建型模式。(使用建造者模式对于用户而言只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。)
适用场景
对象创建步骤很多,而且顺序不固定
对象有非常复杂的内部结构
把复杂对象的创建和适用分离
和工厂模式的区别
建造者模式注重方法的调用顺序,工厂模式注重于创建对象。
建造者模式专注于创建结构复杂的对象,创建过程不同创建出的对象状态也不同,工厂模式创建出来的对象都一样。
关注重点不一样,工厂模式模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建,这个对象,还要知道这个对象由哪些部件组成。
创建者模式根据创建步骤的不同,最后创建出的对象内容也不同
优点
隔离性好,建造和使用分离
扩展性好,建造类之间分离,一定程度上解耦
缺点
产生过多的builder对象
产品类修改,建造产品的类也需要修改
实际应用场景
StringBuilder的append方法
MyBatis的CacheBuilder类的build方法
MyBatis的SqlSessionFactoryBuilder
Spring的BeanDefinitionBuilder
0 条评论
下一页