设计模式的概念:通用的代码设计方案,或者说解决常见问题的可复用方案,通过经验积累和验证总结而出。目的是提高代码的可维护性、扩展性、复用性,同时因为设计模式被广泛认知,本质上也起到减少沟通成本的作用。
设计模式的分类:
一共有23种设计模式,分为三大类:
创建型:
1)单例模式
概念:确保一个类在整个程序运行过程中只有一个实例,提供全局访问点以访问实例。
设计目标:控制对象实例化,节省资源和保证行为一致性。
实现方式:需要一个静态成员变量,和一个静态方法
饿汉式单例模式:类加载时创建单例对象。
懒汉式单例模式:延迟加载,第一次使用时创建单例对象,需要考虑线程安全问题。
双重检查锁懒汉式:
延迟加载,第一次使用时创建单例对象,使用双重检查锁确保线程安全。
//双重检查锁 public class SingletonLazy2 { private SingletonLazy2(){}; //使用volatile修饰,禁止重排序 private static volatile SingletonLazy2 singletonLazy; public static SingletonLazy2 getInstance() { //仅在第一次使用时采用同步处理,提高后续获取实例的速度 if(singletonLazy == null){ synchronized (SingletonLazy2.class){ //抢到锁之后再次判断 if(singletonLazy == null){ singletonLazy = new SingletonLazy2(); } } } return singletonLazy; } }
枚举单例模式:在枚举类中创建单例对象,防止反射和序列化攻击。
非常优雅简洁的写法,INSTANCE是唯一实例。
public enum SingletonEnum {
INSTANCE;
SingletonEnum(){};
public void method(){};
}
应用场景:数据库连接池、线程池。
2)工厂模式
简单工厂模式:创建某一“大类”下不同类的实例。
产品A和产品B拥有共同的父类——产品,工厂就是负责创建产品A、产品B,根据传入参数的不同,可以创建不同产品的实例。
优点:只需关心怎么使用,创建交于工厂。
缺点:新增产品需要修改工厂类,不够灵活且违背开闭原则。
工厂模式:定义一个创建对象的接口(生成子工厂的接口),让子类决定实例化哪个类(子工厂决定生产哪个产品),相比简单工厂,工厂模式使得一个类的实例化延迟到其子类(简单说就是不再是一个大工厂生产所有产品,而是由大工厂底下的小工厂决定自己所生产的产品)。
引入一个非常真实的例子,出处:b站五分钟学设计模式.03.工厂模式_哔哩哔哩_bilibili
优点:扩展性好,新增产品时只需增加新的工厂子类,符合开闭原则。
缺点:仅关注单一的产品类。
抽象工厂模式:打破了工厂和产品一对一的关系,扩展了具体工厂的功能,使其可以生产多个产品大类。简单说,工厂模式创建的是一类对象,提供一个抽象方法;抽象工厂模式创建的是一组对象,提供一个接口。
结构型:
1)代理模式
概念:创建代理对象来控制对原始对象的访问。
设计目标:保护原始类,实现访问控制、缓存、日志记录。
静态代理:编译期已经确定,即需要提前手写代理类。
动态代理:运行时动态生成代理类。
JDK动态代理:基于接口实现,只能代理实现接口的类。
CGLIB动态代理:JDK动态代理的补充方案,代理类继承原始类实现,无需接口。
应用场景:AOP。
行为型:
1)观察者模式(本质就是“发布-订阅”模式)
概念:定义对象间的一种一对多依赖关系(主题对象——观察者),使得每当一个对象状态发生改变时,其相关依赖对象就会得到通知而自动更新。
应用场景:Spring的监听器,消息队列的交换机。
2)责任链模式
概念:把多个处理器串成链,将请求放在链上传递。
优缺点:实现了请求和处理的解耦,请求者不需要关心处理的细节,处理者也不需要关心请求的全貌。缺点是降低系统性能,调试复杂(递归方式,很可能不知道谁在处理请求)。
应用场景:Spring MVC的拦截器。
3)适配器模式
概念:将一个类的接口变成客户端所期待的另一种接口(像一个“转接头”)。
关联使用:把被适配的对象放到适配器里,直接调用。
继承:通过继承为子类调用相关接口。
应用场景:slf4j统一不同日志框架接口。