设计模式面试题

序号内容
1基础面试题
2JVM面试题
3多线程面试题
4MySql面试题
5集合容器面试题
6设计模式面试题
7分布式面试题
8Spring面试题
9SpringBoot面试题
10SpringCloud面试题
11Redis面试题
12RabbitMQ面试题
13ES面试题
14Nginx、Cancal
15Mybatis面试题
16消息队列面试题
17网络面试题
18Linux、Kubenetes面试题
19Netty面试题

对设计模式的理解

设计模式的作用

用别人已经实践过的经验,避免我们采坑,提高代码的复用。

设计模式的原则

单一原则:这意味着一个类应该只做一件事,并且只负责一个功能点或业务逻辑。

开闭原则:软件实体对扩展开放,对修改关闭。当应用需求改变的时候,在不修改源码的前提下可以扩展模块的功能,使其满足新的需求。

里氏代换原则:子类应当能够替换其父类并保留原有功能,确保继承的正确使用。

依赖倒转原则:要面向接口编程,不要面向实现编程

接口隔离原则:将接口细化,客户端不应依赖不必要的接口,降低耦合度

迪米特法则:个对象应尽可能少地知道其他对象的细节,降低依赖,增强模块独立性。

合成复用原则:优先使用组合而非继承来实现功能的复用,提高代码灵活性和可维护性。

3、设计模式的分类

设计模式中有一个类型是创建型模式,专门用来做对象的创建,例如单例、原型。

结构型模式,把类或对象结合在一起形成一个更大的结构。例如代理、装饰者、适配器

行为型模式,类和对象如何交互,及划分责任和算法。例如责任链、策略、观察者

单例模式

单例的作用

某个类在软件生命周期只有唯一的一个对象。通过线程同步来控制资源的并发访问,控制实例产生的数量,达到节约资源,

实现方式

饿汉式

在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。缺点是不支持延迟加载实例和占用资源。

//声明一个final的成员变量,私有构造方法,提供一个静态公有方法。
public class Singleton {
    private Singleton(){}
    private static final Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}

懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载(第一次调用的时候再创建,创建完后就不需要在创建了)。多线程情况下,会导致获取到的对象不是初始的那个对象。

public class Singleton {
    //成员变量不加载对象,私有构造方法,提供一个静态的获取对象的方法。
    private Singleton(){}
    private static Singleton instance;

    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

双重检测

双重检测方式既支持延迟加载、又支持高并发的单例实现方式。不加第一个if (uniqueInstance == null) {} 判断,或导致多个请求阻塞到synchronized,影响效率

使用volatile防止指令重新排序。创建实例的步骤是:

1、分配内存空间。2、创建实例对象。3、把这个内存地址的值赋给变量引用。

如果没有防止指令重新排序,先执行到第三步,这个时候另外一个线程再获取到的对象就是空的。或造成对象为空的异常。

public class Singleton {
    //加volatile能禁止指令重排。
	private volatile static Singleton uniqueInstance;
	private Singleton() {
	}
	public static Singleton getUniqueInstance() {
   		//先判断对象是否已经实例过,没有实例化过才进入加锁代码
    	if (uniqueInstance == null) {
        	//类对象加锁
        	synchronized (Singleton.class) {
            	if (uniqueInstance == null) {
                	uniqueInstance = new Singleton();
            	}
        	}
    	}
    }
    return uniqueInstance;
}

静态内部类

当第一次加载 Singleton 类时并不会初始化 instance,只有在第一次调用 Singleton 的 getInstance 方法时才会导致 instance 被初始化。

第一次调用 getInstance 方法时会导致虚拟机加载 Instance 类,这种方式不仅能保证线程安全,也能够保证单例对象唯一,同时也延迟了单例的实例化。

public class Singleton {
    private Singleton(){}

    private static class Instance {
        private static final Singleton instance = new Singleton();
    } 

    public static Singleton getInstance(){
        return Instance.instance;
    }
}

枚举

保证了实例创建的线程安全性和实例的唯一性。

public enum Singleton {
    INSTANCE;
}

DCL (双重检测)单例模式设计为什么需要 volatile 修饰实例对象

双重检测模式下的单例存在不完整对象的问题。这是因为指令重排导致的。我们使用 new Singleton(); 创建对象时,这个操作不是原子性的。new 的这个段代码最终会被编译成三个指令:为对象分配内存空间、初始化对象、把实例对象复制给instance引用。

根据重新排序规则,在不影响单线程执行结果的情况下,两个不存在依赖关系的指令允许重排。这样就导致其他线程可能拿到一个不完整的对象。也就是instance已经分配了引用实例,但是这个实例初始化的指令还没执行。在instance变量上加volatile,可以根据volatile的内存屏障机制来避免指令重排。

反射可以破解单例,这个怎么办

在反射获取单例对象之前,已经存在了单例对象的前提下。通过反射获取到对应的实例对象,和单例模式下获取到的实例对象不是同一个。反射破解单例是通过反射执行单例类私有的构造方法。可以加判断,判断单例对象不为空的情况下,且方法调用到了私有构造方法。在私有的构造方法中throw 一个异常,这样就无法通过反射去获取一个单例对象了。

以双重检测为例:

private Singleton() {
	if (uniqueInstance != null) {
        throw new RuntimeException("");
    }
}

在反射获取单例对象之前,单例对象还没有被创建出来。可以通过加标识的方法,判断是否已经获取过单例对象。但是反射可以修改flag的值。

可以将flag 改为枚举类型,底层源码中通过反射获取枚举类会抛异常。

private volatile static Singleton uniqueInstance;
private static boolean flag = falseprivate Singleton() {
	if (flag == false) {
       //说明是第一次创建。flag = true 之后就不再是第一次创建了,后面的需要获取
        flag = true;
        uniqueInstance = this;
    } else {
        throw new RuntimeException("");
    }
}

防止序列和反序列化破解单例

需要在单例类中重写readResolve方法。

//直接加到单例类中
private volatile static Singleton uniqueInstance;
private Object readResolve() {
    return uniqueInstance;
}

单例在哪些场景中用到

1、springBean

2、很多工厂类,例如BeanFactory ,SqlSessionFactory,用到了单例

3、保存配置文件的对象。例如springboot 的 xxxxAutoConfiguration

4、数据库连接池。

工厂模式

1、工厂模式的作用就是生产对象的。针对对象的复杂程度,开闭原则。工厂模式分为 简单工厂,工厂方法,抽象工厂。

简单工厂:用来生产同一等级结构中的任意产品。只有一个工厂,不同的产品需要不同额外参数的时候 不支持,扩展性差。

interface Character{
    void run();
}
class A implement Character{
    @Override
    public void run(){sout("AAAAAAAAAA")};
}
class B implement Character{
    @Override
    public void run(){sout("BBBBBBBBBB")};
}
class CharacterFactory{
	public static Character optCharacter(String type) {
        if ("A".equals(type){
            return new A();
        } else if ("B".equals(type){
            return new B();
        } else {
           sout("-----"); 
        }   
    }
}

工厂方法:用来生产同一等级结构中的固定产品。工厂方法支持扩展性,但是比简单工厂复杂。

public static interface Character{
    void run();
}
public static class A implement Character{
    @Override
    public void run(){sout("AAAAAAAAAA")};
}
public static class B implement Character{
    @Override
    public void run(){sout("BBBBBBBBBB")};
}
//----------------------------------------
public static interface CharacterFactory(){
    public Character createCharacter(); 
}
public static class AFactory implement CharacterFactory{
     @Override
     public Character createCharacter(){
         return new A();
     }
}
public static class BFactory implement CharacterFactory{
     @Override
     public Character createCharacter(){
         return new B();
     }
}

抽象工厂:用来生产不同产品簇中的所有产品。对于增加新的产品无能为力,支持增加产品簇。

public static interface Character{
    void run();
}
public static class A implement Character{
    @Override
    public void run(){sout("AAAAAAAAAA")};
}
public static class B implement Character{
    @Override
    public void run(){sout("BBBBBBBBBB")};
}
public static interface CharacterFactory(){
    public Character createCharacter(); 
}
public static class AFactory implement CharacterFactory{
     @Override
     public Character createCharacter(){
         return new A();
     }
}
public static class BFactory implement CharacterFactory{
     @Override
     public Character createCharacter(){
         return new B();
     }
}
//----------------------------------------
public static interface CharacterOther{
    void run();
}
public static class C implement CharacterOther{
    @Override
    public void run(){sout("CCCCCCCCCCC")};
}
public static class D implement CharacterOther{
    @Override
    public void run(){sout("DDDDDDDDDDD")};
}
public static interface CharacterOtherFactory(){
    public CharacterOther createCharacterOther(); 
}
public static class CFactory implement CharacterOtherFactory{
     @Override
     public CharacterOther createCharacter(){
         return new C();
     }
}
public static class DFactory implement CharacterOtherFactory{
     @Override
     public CharacterOther createCharacter(){
         return new D();
     }
}

//另一个接口,另外两个接口的方法。
public static interface AbstractComponentFactory{
    Character createCharacter(); 
    CharacterOther createCharacterOther(); 
}
//例如A和C为一簇,B和D为一簇。调用的需要分为A、C一个实现,B、D一个实现。
public static class FirstFactory implement AbstractComponentFactory(){
     @Override
     public Character createCharacter(){
          return new AFactory.createCharacter();
     }
     @Override
     public CharacterOther createCharacterOther(){
          return new CFactory.createCharacterOther();
     }
}
public static class SecondFactory implement AbstractComponentFactory(){
     @Override
     public Character createCharacter(){
          return new BFactory.createCharacter();
     }
     @Override
     public CharacterOther createCharacterOther(){
          return new DFactory.createCharacterOther();
     }
}

建造者模式

建造者模式的作用是解决创建对象比较复杂的情况。一个对象不能直接被new 出来,

策略模式

什么是策略模式

是一种行为型模式。它将行为和对象分开,将行为定义为一个行为接口和具体行为的实现。

策略模式的优缺点

1、避免了多重 if else

2、算法可以自由切换,扩展性良好

3、策略类活很多

4、客户端必须知道所有的策略类,并自行决定使用哪一个策略类

作用和实现

1、策略模式的作用

策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。它可以避免使用多重条件转移语句(多if判断)。

2、实现方式。

//定义一个接口
public interface Character {
    void run(String params);
}
//接口实现类,需要交给Spring管理
@Component
public class TestAtype implements Character{
	@Override
    public void run(String params) {
    	sout("AAAAAAAAA");
    }
}
@Component
public class TestBtype implements Character{
	@Override
    public void run(String params) {
    	sout("BBBBBBBBB");
    }
}
//创建一个策略管理类,也需要交给Spring管理
@Component
public class StrategyFactory {
    //Spring会自动将Strategy接口的实现类注入到这个Map中,key为bean id,value值则为对应的策略实现类。
    @Autowired
    private Map<String, Character> characterMap;
    //type就是接口实现类的类名(testAtype,或者testBtype),params任意参数,可以为空。
    public void test(String type,String params){

        Character character = Optional.ofNullable(characterMap.get(type)).orElseThrow(() ->
                new RuntimeException("类型为空"));

        return character.run(params);
    }
}

原型模式

原型模式的作用

快速的创建对象。例如 spring 中的Scpoe prototype。

深克隆、浅克隆

浅克隆:只克隆本对象,对对象内部的数组、已用对象都不克隆,克隆对象和被克隆对象都指向原生对象的内部元素地址,如果原生对象的某个基本属性值被改变了,克隆对象的这个基本属性值也会改变。如果克隆对象的基本属性值改变,被克隆对象的基本属性值不会变。(通过实现Cloneable接口)

深克隆:深克隆就是要克隆对象的所有引用的对象都复制一遍。使用序列化和反序列化实现深克隆。

深克隆相比于浅拷贝速度慢并且开销大,但是拷贝前后两个对象互不影响。(通过实现Serilable接口)

代理模式

静态代理

为了对已有的方法的功能进行增强。或者说在不修改原来代码的情况下更灵活的扩展其功能。

实现方法:一个接口,一个接口实现类,接口实现类中有自己的业务功能。另一个是代理类,代理类也实现这个接口,在接口的实现中,通过接口调用已经写好的业务逻辑,在这个已经写好的方法的上面或者下面增加代码,增强已经写好的代码的功能。

缺点:

  • 静态代理类和委托类实现了相同的接口,代码重复。

  • 静态代理是在编译时(开发阶段)就将代理类完成的。每一个目标类都需要一个代理类,会产生大量代理。

// 定义一个接口  
public interface UserService {  
    void doSomething();  
}  
  
// 实现接口的类  
public class UserServiceImpl implements UserService {  
    @Override  
    public void doSomething() {  
        System.out.println("UserServiceImpl doing something...");  
    }  
}
// 静态代理类  这个类中的doSomething是为了给UserServiceImpl类中的doSomething功能做增强。
// 也可以控制访问权限、优化系统性能、增强功能、提供稳定网络连接以及保护用户隐私和安全
public class UserServiceStaticProxy implements UserService {  
    private UserService userService;  
  
    public UserServiceStaticProxy(UserService userService) {  
        this.userService = userService;  
    }  
  
    @Override  
    public void doSomething() {  
        System.out.println("Before doSomething method");  
        userService.doSomething();  
        System.out.println("After doSomething method");  
    }  
}

动态代理

JDK动态代理

JDK动态代理在代理被执行的时候会生成一个全新的字节码代理类。这个代理类继承了Proxy和实现了被代理业务的接口。通过代码中传参的类加载器,将这个代理类加载到JVM内存中,使这个代理类变成可用类。

代理类在没有被任何对象引用的时候,且生命周期完了之后会被jvm的垃圾回收器回收。

//接口
public interface Subject {
    /**
     * 接口方法
     */
    void doSomething();

    /**
     * sayHello
     *
     * @param name name
     * @return string
     */
    String sayHello(String name);

}
//接口实现类
public class RealSubject implements Subject{

    @Override
    public void doSomething() {
        System.out.println("RealSubject do something");
    }

    @Override
    public String sayHello(String name) {
        System.out.println("RealSubject sayHello");
        return "hello-" + name;
    }

}
//代理工厂
public class JdkDynamicProxyFactory {
    /**
     * 创建target类的代理对象
     * 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法
     * @param <T> 泛型
     * @return 代理对象
     */
    public static <T> T getProxy(Object target) {
        // 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】
        Object proxyInstance = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("执行目标方法前");
                        // 执行目标方法
                        Object result = method.invoke(target, args);
                        System.out.println("执行目标方法后");
                        // 返回目标方法的执行结果
                        return result;
                    }
                });
        // 返回代理对象
        return (T) proxyInstance;
    }
}
//测试类
public static void main(String[] args) {
    // 保存生成的代理类的字节码文件
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    // 目标对象
    Subject subject = new RealSubject();
    // 使用JDK动态代理为【target对象】创建代理对象
    Subject proxy = MyHandler.getProxy(subject);
    // 调用代理对象的方法
    proxy.doSomething();
    System.out.println("----------------------------------------");
    proxy.sayHello("test");

}
//生成的代理对象,继承了Proxy类,实现了业务接口Subject
public final class $Proxy0 extends Proxy implements Subject {
    // 在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;
    
    // 以静态代码块的形式为属性赋值
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.thy.program.test.Subject").getMethod("doSomething");
            m4 = Class.forName("com.thy.program.test.Subject").getMethod("sayHello", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            // 这里的supper是指的Proxy类,调用【Proxy类】的【h属性】的invoke方法执行
          	// 重点:【注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
          	// 这里也就体现了创建代理对象时为什么需要传入【InvocationHandler】,以及为什么调用代理对象的方法时都是执行的invoke方法
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void doSomething() throws  {
        try {
            // 调用了【Proxy.h属性】的invoke方法
          	// 注意这里的 super.h 其实就是我们创建代理对象是传入的【InvocationHandler】,
            // proxy对象中定义了protected InvocationHandler h;
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String sayHello(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
	
}
JdkDynamicProxyFactory的Proxy.newProxyInstance是如何创建代理对象的

1、检查接口,判断传入的参数是否有效。

2、创建代理文件的字节码文件。通过invoke生成,每次调用不同的接口生成不同的文件。

3、将生成的代理文件加载到jvm内存中。

4、类加载完成后,Proxy.newProxyInstance会调用代理的构造方法创建一个新的代理对象。

5、返回创建的这个实例对象

为什么传类加载器

动态代理类则是在【运行过程中动态生成的类】,需要被加载到JVM内存中才能被执行。所以需要传类加载器将生成的代理文件加载到内存

为什么传targt的类接口实现

生成的代理类已经继承了Proxy类,Java中时单继承的,动态代理类还需要实现目标接口,来重写接口方法。所以需要传接口。通过实现这个接口,来重写目标方法。

为什么传InvocationHandler

生成的代理类需要重写目标接口的方法,在重写的方法中需要执行super.h.invoke,super.h.就是只Proxy类的InvocationHandler属性,根据这个对象属性的invoke方法(反射)来重写目标接口的实现。

cglib动态代理

//业务类
public class MyClass {
    public void myMethod() {
        System.out.println("This is my method.");
    }
}
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
//拦截器
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method call.");
        Object result = proxy.invokeSuper(obj, args); // 调用父类(即原始类)的方法
        System.out.println("After method call.");
        return result;
    }
}
//调用
public class CGLIBProxyExample {
    public static void main(String[] args) {

        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");

        // 创建Enhancer对象
        Enhancer enhancer = new Enhancer();

        // 设置父类,即我们要代理的类
        enhancer.setSuperclass(MyClass.class);

        // 设置回调函数,即代理对象的方法调用时应该执行的操作
        enhancer.setCallback(new MyMethodInterceptor());

        // 创建代理对象
        MyClass proxyInstance = (MyClass) enhancer.create();

        // 调用代理对象的方法,这将触发MyMethodInterceptor中的intercept方法
        proxyInstance.myMethod();
    }
}

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.thy.program.cglib;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyClass$$EnhancerByCGLIB$$bdc0f2b9 extends MyClass implements Factory {
  ...
    final void CGLIB$myMethod$0() {
        super.myMethod();
    }
    public final void myMethod() {
        //MethodInterceptor 对象是在调用代理的时候Enhancer设置的Callback参数。
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            //最终执行的是自定义的拦截器的MyMethodInterceptor的intercept方法
            var10000.intercept(this, CGLIB$myMethod$0$Method, CGLIB$emptyArgs, CGLIB$myMethod$0$Proxy);
        } else {
            super.myMethod();
        }
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

   ...
}

jdk动态代理和cglib动态代理的区别

1、jdk动态代理,代理的是接口。cglib动态代理,代理的是类,需要引入cglib第三方类库。

2、dk动态代理是通过代理类Proxy 的InvocationHandler的反射实现动态代理。cglib动态代理是通过目标类生成一个子类的方式来实现动态代理。

3、cglib动态代理是通过生成一个子类,这个代理的子类继承父类,所以父类的方法不能添加final。

装饰器模式

在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)。装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态地扩展类的功能。

装饰器模式和代理模式的区别

代理模式是一种保护对象免受意外变化的影响,即控制对另一个对象的访问。

装饰器模式是一种动态地给一个对象添加新的功能,它是一种对象结构型模式,它增加了类的复杂性,但不会增加类的数量。装饰器模式提供了一种动态的、运行时添加新的行为的方式。通过这种方式,可以在运行时改变对象的行为,而不需要修改其源代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值