# 设计模式面试问题
# 常见的设计模式有那些
# 使用了那些设计模式
# 单例设计模式的两种实现与区别
# 抽象工厂与策略模式的作用
# spring的设计模式体现
# Java中的单例模式如何实现
懒汉式单例模式
懒汉式,表示不提前创建对象实例,而是在需要的时候再创建,代码如下。
public class Singleton {
private static Singleton instance;
private Singleton() {
}
// synchronized方法,多线程情况下保证单例对象唯一
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
其中,对getInstance()方法,增加了synchronized同步关键字, 目的是为了避免在多线程环境下同一时刻调用该方法导致出现多实例问题(线程的并行执行特性带来的线程安全性问题)。
优点: 只有在使用时才会实例化单例,一定程度上节约了内存资源。 缺点: 第一次加载时要立即实例化,反应稍慢。每次调用getInstance()方法都会进行同步,这样会消耗不必要的资源。这种模式一般不建议使用。
DCL双重检查式单例
DCL双重检查式单例模式,是基于饿汉式单例模式的性能优化版本。
/**
* DCL实现单例模式
*/
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 两层判空,第一层是为了避免不必要的同步
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {// 第二层是为了在null的情况下创建实例
instance = new Singleton();
}
}
}
return instance;
}
}
饿汉式单例模式
在类加载的时候不创建单例实例。只有在第一次请求实例的时候的时候创建,并且只在第一次创建后,以后不再创建该类的实例。
/**
* 饿汉式实现单例模式
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
静态内部类
静态内部类,是基于饿汉式模式下的优化。 第一次加载Singleton类时不会初始化instance,只有在第一次调用getInstance()方法时,虚拟机会加载SingletonHolder类, 初始化instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。
/**
* 静态内部类实现单例模式
*/
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 静态内部类
*/
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
}
这种方式既保证线程安全,单例对象的唯一,也延迟了单例的初始化,推荐使用这种方式来实现单例模式。 静态内部类不会因为外部内的加载而加载,同时静态内部类的加载不需要依附外部类,在使用时才加载,不过在加载静态内部类的过程中也会加载外部类
基于枚举实现单例
用枚举来实现单例,是最简单的方式。这种实现方式通过Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
public enum SingletonEnum {
INSTANCE;
public void execute(){
System.out.println("begin execute");
}
public static void main(String[] args) {
SingletonEnum.INSTANCE.execute();
}
}
# Cglib 和 jdk 动态代理的区别
- Jdk动态代理:利用拦截器(必须实现 InvocationHandler )加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
- Cglib动态代理:利用 ASM 框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理。
# 什么时候用 cglib 什么时候用 JDK 动态代理?
- 目标对象生成了接口 默认用 JDK 动态代理
- 如果目标对象使用了接口,可以强制使用 cglib
- 如果目标对象没有实现接口,必须采用 cglib 库, Spring 会自动在 JDK 动态代理和 cglib 之间转换
# JDK 动态代理和 cglib 字节码生成的区别?
- JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。
- Cglib 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要设置为 final ,对于 final 类或方法,是无法继承的
# Spring 如何选择是用 JDK 还是 cglib?
- 当 bean 实现接口时,会用 JDK 代理模式
- 当 bean 没有实现接口,用 cglib实现
- 可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class="true"/>)
# SpringBoot为什么默认使用Cglib动态代理
- 性能和速度: Cglib动态代理在性能上通常比标准的JDK动态代理更快。Cglib直接通过字节码生成子类来实现代理,避免了一些反射操作,因此在方法调用等方面通常更加高效。
- 无需接口:JDK动态代理要求目标类必须实现一个接口,而Cglib动态代理不需要。这使得Cglib更适用于那些没有接口的类,从而扩展了动态代理的适用范围。
- 无侵入性: Spring Boot选择Cglib动态代理可以使你的类无需实现任何接口或继承特定的类,从而减少了对源代码的侵入性。这对于集成第三方库或需要代理的现有类特别有用。
- 方便集成: Spring Boot默认提供了Cglib相关的依赖,因此在应用程序中使用Cglib动态代理非常方便。
# 策略模式和适配器模式的区别
策略模式(Strategy Pattern):定义一系列算法,将每个算法封装起来,并使它们可以互换。该模式使得算法可以独立于使用它的客户端而变化。
在策略模式中,定义了一个抽象策略类和多个实现策略类,客户端通过实例化不同的实现策略类来选择不同的算法实现。由于实现策略类之间没有继承关系,因此可以很容易地新增和替换算法。
策略模式通常用于需要动态切换算法的场景,例如网络请求中根据不同的场景切换不同的请求方式;或者对于一个通用接口,有多种不同的实现方式可供选择的情况。
适配器模式(Adapter Pattern):将一个类的接口转换成客户期望的另外一个接口。适配器模式使得原本由于接口不兼容而不能在一起工作的类可以一起工作。
在适配器模式中,需要定义一个适配器类,该适配器类实现一个目标接口,同时持有一个需要适配的适配者类对象。通过适配器类的中间转换实现了适配器类与目标接口的兼容。
适配器模式通常用于需要使用不同的类或者库时,希望通过某种方式将它们兼容,而不需要修改原有代码的场景。
# 装饰器模式
- 动态地给对象添加一些额外的属性或者行为
- 和继承相比,装饰器模式更加灵活
装饰器模式使用场景:当需要修改原有的功能,但是不想直接修改原有的代码,就可以设计一个装饰器Decorator类在原有的代码的外面,这样可以在不修改原有的类的基础上扩展新的功能
Spring中配置DataSource时 ,DataSource可以是不同的数据库和数据源.为了在少修改原有类的代码下动态切换不同的数据源,这时就用到了装饰器模式
Spring中含有Wrapper和含有Decorator的类都用到了装时期模式,都是动态地给一个对象添加一些额外的属性或者功能
# 适配器模式
- 是结构型模式,也是各种结构型模式的起源
- 将一个接口转换为调用方需要的接口
- 适配器使得接口不兼容的类之间可以一起工作.适配器又被称为包装器Wrapper
Spring AOP中的增强和通知Advice使用了适配器模式,接口是AdvisorAdapter
Spring MVC中 ,DispatchServlet根据请求信息调用HanlderMapping, 解析请求对应的Handler, 解析到对应的Handler后, 开始由HandlerAdapter适配器进行处理,HandlerAdapter作为期望接口,具体的适配器实现类对具体目标类进行适配 .controller 作为需要适配的类。通过使用适配器AdapterHandler可以对Spring MVC中众多类型的Controller通过不同的方法对请求进行处理
# 观察者模式
- 是一种对象行为模式
- 表示的是一种对象和对象之间具有依赖关系,当一个对象发生改变,依赖于这个对象的对象也会发生改变。
Spring事件驱动模型就是基于观察者模式实现的,Spring事件驱动模型可以在很多应用场景中解耦代码,比如每次添加商品时都需要更新商品索引,这时就可以使用观察者模式
实现一个继承自ApplicationEvent的事件类,并写出相应的构造函数,实现ApplicationListener接口,重写onApplicationEvent() 方法
使用事件发布者发布消息: 使用ApplicationEventPublisher的publishEvent() 方法,重写publishEvent() 方法发布消息
# 模板方法模式
- 是一种行为型模式,基于继承的代码复用
- 定义一个操作的算法骨架,将一些实现步骤延迟到子类中
- 模板方法使得子类可以不改变一个算法结构的情况下即可重新定义算法的某些特定步骤的实现方式
Spring中以Template结尾的类,比如jdbcTemplate等,都是使用了模板方法模式。 通常情况下,都是使用继承来实现模板模式,在Spring中,使用了Callback与模板方法相结合的方式,既达到了代码复用的效果,又增加了系统的灵活性