【Java超高频面试题&设计模式】设计模式相关高频面试题汇总

简介:

本期文章将汇总java设计模式相关的高频面试题,给最近需要找工作的朋友们总结一波,帮助大家全面掌握设计模式的核心知识点,提升竞争力,为你的面试之旅保驾护航!


常见的设计模式说一下(高频)

单例模式:一个类只能有一个实例,分为饿汉模式(迫切加载)和懒汉模式(延迟加载)和枚举。

工厂模式:隐藏了产品的复杂创建过程,实现生产功能的复用,让产品生产更加高效。分为简单工厂(需要来回切换生产线),工厂方法(开设新的生产线),抽象工厂(制定创建产品的接口,让子工厂选择创建哪种产品)

在Spring中各种的BeanFactory创建bean都用到了

模板模式:定义一个算法骨架或者算法的流程,而不同的实例实现方式不同,将某个或多个具体的实现延迟到子类中,比如RedisTemplate实现了RedisOperations,ElasticSearchTemplate实现了ElasticsearchOperations

代理模式:不直接使用实际对象,通过调用代理对象间接调用实际对象,主要用作对实际对象的增强,分为静态代理,JDK动态代理,CGLIB动态代理比如Spring的AOP原理就是动态代理,当目标对象实现了接口会使用JDK动态代理,没有实现接口会使用CGLIB动态代理

适配器模式:将不兼容的接口转换为可兼容的接口的中间类,比如HandlerInterceptorAdapter ,我们定义拦截器时不需要覆写HandlerInterceptor中的所有方法,因为适配器类帮我们做了空实现。但JDK1.8之后,给接口中增加了默认方法,可以有方法体,因此这些适配器类已经失去作用了

观察者模式:当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,比如Spring中的ApplicationListener

什么是单例,如何实现

一个类只能有一个实例,主要用于需要频繁使用的对象避免频繁初始化和销毁来提高性能,或者资源需要相互通信的环境

主要实现方式有,饿汉模式,懒汉模式,枚举,静态内部类

饿汉模式,构造器私有化,提供静态获取实例的方法。是在类加载过程中就将这个单例对象实例化,需要将构造方法私有化,定义一个成员变量并new一个该类的实例作为初始值,提供一个公共的静态方法获取这个实例

//单利模式 饿汉 :工具类
class JDBCUtil{
    //2.定义成员变量,new实例作为初始值 ,饿汉:一来就创建实例,赋初始值
    private static JDBCUtil instance = new JDBCUtil();
    //1.私有构造器
    private JDBCUtil(){}
    //3.提供获取实例方法
    public static JDBCUtil getInstance(){
        return instance;
    }
}
System.out.println(JDBCUtil.getInstance());

懒汉模式,是在使用时才创建这个单例对象,需要将构造方法私有化,定义一个该类的成员变量不赋初始值,提供一个获取实例的公共静态方法。特别注意这个方法需要保证多线程环境下的并发安全性,可以通过DCL加volatile关键字来解决。

//单利模式 懒汉 :工具类
class JDBCUtil{
    //1.定义成员变量,不赋初始值
    private static volatile JDBCUtil instance  = null;
    //1.私有构造器
    private JDBCUtil(){}
    //2.提供获取实例方法
    public static  JDBCUtil getInstance(){
            //第一次调用时创建对象
            if(instance == null){
                //同步代码块,保证创建实例的代码的原子性,只会创建一个实例
                synchronized (JDBCUtil.class){
                    if(instance == null){
                        instance = new JDBCUtil();
                    }
                }
            }
        return instance;
    }
}
System.out.println(JDBCUtil.getInstance());

枚举,直接在枚举中定义字段,它就是单例并且线程安全的

//单利:枚举
enum JDBCUtil{
    INSTANCE;
}
System.out.println(JDBCUtil.INSTANCE);

静态内部类,在类中搞一个静态内部类,在静态内部类中搞一个目标类的静态成员变量并且new一个实例作为初始值。然后在目标类中定义一个获取实例的静态方法,方法返回的就是静态内部类中的成员变量。这种方式能保证线程安全,也能实现延迟加载。缺点是这种方式传参不太方便。

模板模式的作用

定义一个算法骨架,而将某个或多个具体的实现延迟到子类中,使得子类可以在不修改当前算法的结构情况下,重新定义当前算法的某些特定步骤

比如考试中所有考生的试卷都一样,答案由每个考生自己完成

什么是适配器模式

将不兼容的接口转换为可兼容的接口的中间类

比如HandlerInterceptorAdapter ,我们定义拦截器时不需要覆写HandlerInterceptor中的所有方法,因为适配器类帮我们做了空实现。但JDK1.8之后,给接口中增加了默认方法,可以有方法体,因此这些适配器类已经失去作用了

什么是代理模式?有几种代理?

不直接使用实际对象,通过调用代理对象间接调用实际对象,主要用作对实际对象的增强,分为静态代理JDK动态代理CGLIB动态代理

JDK动态代理和CGLIB动态代理的区别?(高频)

JDK动态代理是jdk提供的,我们可以直接使用,而CGLIB需要导入第三方库

JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用目标方法前调用InvokeHandler来处理

CGLIB动态代理是先加载目标类的class文件,然后修改其字节码生成子类来实现的

代理模式详解

所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理对象,来间接的调用实际的对象。这样我们可以在代理调用被代理对象之前,之后,报错加入自己的逻辑。

举例:经纪人和明星 ,菜鸟驿站代收快递。

代码举例

1. 抽象的基类

//抽象基类,定义流程 - 喝水的流程
public abstract class AbstractDrinkWaterTemplate {

    //喝水的流程
    void drinkWaterProcess(){
        //烧
        System.out.println("烧水...");
        //倒
        System.out.println("倒水...");
        //泡
        pourWater();
        //喝
        drinkWater();
    }
    abstract void pourWater();
    abstract void drinkWater();
}

2. 子类实现一个Coffee类

public class Coffee extends AbstractDrinkWaterTemplate{
    @Override
    void pourWater() {
        System.out.println("泡咖啡...");
    }

    @Override
    void drinkWater() {
        System.out.println("喝咖啡...");
    }
}

3. 子类实现Team类

public class Team extends AbstractDrinkWaterTemplate {
    @Override
    void pourWater() {
        System.out.println("泡茶...");
    }

    @Override
    void drinkWater() {
        System.out.println("喝茶...");
    }
}

测试代码

public class DrinkWaterTemplateTest {
    public static void main(String[] args) {
        new Coffee().drinkWaterProcess();
        System.out.println("-----------------");
        new Team().drinkWaterProcess();
    }
}

框架举例:JdbcTemplate ElasticSearchTemplate

1.静态代理

代理分为动态代理与静态代理。我们先从简单的静态代理开始研究。

实体接口

//主体
public interface Subject {
    //访问
    void visit();
}

被代理真实对象

public class RealSubject implements Subject {      
    private String name = "我是真实对象";
    @Override     
    public void visit() {         
        //真实对象执行...
        System.out.println(name);     
    } 
}

代理类

//代理对象
public class ProxySubject implements Subject {
    //代理对象持有真实对象
    private Subject subject;

    //构造代理对象需要传入被代理的真实对象
    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void visit() {
        //1.执行前
        //2.调用真实对象的方法
        subject.visit();
        //3.执行后
    }
}

创建代理对象

public class Client {

    public static void main(String[] args) {
        //创建代理
        ProxySubject subject = new ProxySubject(new RealSubject());
        subject.visit();
    }
}

静态代理的优缺点 :每个类都要创建一个代理类,对于项目开发而言需要为很多类都创建代理类,使用静态代理无疑是一个很麻烦的工作,我们需要在代码运行的过程中动态生成代理类 - 动态代理

2.JDK动态代理

动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。Java1.3就提供了动态代理,让咱们可以在代码运行期去实现一个接口的代理实例。这个功能在刚出来时,几乎没有太大实际用途,但是后来发现,它简直就是为实现AOP量身打造。

但是大家注意了,jdk的动态代理只允许完成有接口的代理,但是在我们开发的很多时候,可能还是会遇到去代理没有接口的类(创建的代理对象就是这个类的子类),比如咱们学习的Hibernate中的延时加载就是使用的这种方式。那么Spring是怎么解决这个问题的呢?Spring使用两种方式来完成动态代理:

  • 如果代理的类有接口,使用JDK的动态代理模式,

  • 如果代理的类没有接口,使用CGLIB的动态代理模式。

所以,现在咱们要开始来学习一下怎么使用这两种模式来完成动态代理。

注:JDK动态代理只能代理有接口的类,下面是完成JDK代理的主要类:

java.lang.reflect.Proxy (可以到jdk文档中找到这个类)

java.lang.reflect.InvocationHandler:代理调用处理程序的接口

创建JDKProxy类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * InvocationHandler:是代理实例的调用处理程序 实现的接口。 
 */
public class JdkProxy implements InvocationHandler{
	//定义真实主题角色:目标对象
	private Object targetObject;
	//传入事务管理器
	private TxManager txManager;
	public JdkProxy(Object targetObject,TxManager txManager) {
		this.targetObject = targetObject;
		this.txManager = txManager;
	}
	
	/**
	 * proxy:经过jdk的代理对象(基本上没有作用)
	 * method:实际执行的方法
	 * args:方法中的参数
	 */
	@Override
	public Object invoke(Object targetObject, Method method, Object[] args) throws Throwable {
		Object result = null; //返回的结果
		try {
			txManager.begin();
			result = method.invoke(targetObject, args); //执行直接对象的方法
			txManager.commit();
		} catch (Exception e) {
			txManager.rollback();
			e.printStackTrace();
		}finally{
			txManager.close();
		}
		return result;
	}
	
	/**
	 * 创建一个代理对象
	 * @return
	 */
	public Object createProxy(){
		return Proxy.newProxyInstance(
				this.getClass().getClassLoader(),  //类加载器,只要拿到一个即可
				targetObject.getClass().getInterfaces() , //实现类的接口集合(因为一个类可以实现多个接口)
				this //代理实例的调用处理程序(InvocationHandler的实现类)
			);
	}
}

测试功能

@Test
public void testProxy() throws Exception {
	User user = new User();
	//真实主题角色对象
	IEmployeeService employeeService = new EmployeeServiceImpl();
	//事务管理器
	TxManager txManager = new TxManager();
	//创建咱们自定义的一个处理代理功能类(这个类中我们加了一个方法可以直接创建代理对象)
	JdkProxy jdkProxy = new JdkProxy(employeeService,txManager);
	//获取代理对象
	IEmployeeService proxy = (IEmployeeService)jdkProxy.createProxy();
	proxy.save(user);
}

3.CGLIB动态代理

Cglib类似于javassist-3.18.1-GA.jar功能字节码增强,原来Hibernate3.2之前就是使用cglib来进行字节码增强,下面是完成CGLIB的类:

  • org.springframework.cglib.proxy.Enhancer; 增强器

  • org.springframework.cglib.proxy.MethodInterceptor; 方法切面(代理实例处理方法功能的接口)

  • org.springframework.cglib.proxy.MethodProxy;

CGLIBProxy类代码

import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor{

	//定义参数,接收真实的目标对象
	private Object targetObject;
	//事务对象
	private TxManager txManager;
	
	public CglibProxy(Object targetObject,TxManager txManager) {
		this.targetObject = targetObject;
		this.txManager = txManager;
	}
	
	/**
	 * proxyObject:CGLIB代理后的对象,一般不用
	 * method:真实对象的方法
	 * args:方法的参数
	 * methodProxy:CGLIB代理后的方法,一般不用
	 */
	@Override
	public Object intercept(Object proxyObject, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
		Object result = null; //返回的结果
		try {
			txManager.begin();
			result = method.invoke(targetObject, args); //执行直接对象的方法
			txManager.commit();
		} catch (Exception e) {
			txManager.rollback();
			e.printStackTrace();
		}finally{
			txManager.close();
		}
		return result;
	}
	
	/**
	 * 创建一个代理对象
	 * @return
	 */
	public Object createProxy(){
		//创建增强器
		Enhancer enhancer = new Enhancer();
		//创建的代理就是咱们真实目标对象的子类
		enhancer.setSuperclass(targetObject.getClass());
		//MethodInterceptor就是一个Callback回调
		enhancer.setCallback(this);
		//创建一个代理对象并返回
		return enhancer.create();
	} 
}

测试代码

@Test
	public void testProxy() throws Exception {
		User user = new User();
		//真实主题角色对象
		EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
		//事务管理器
		TxManager txManager = new TxManager();
		//创建Cglib代理对象
		CglibProxy cglibProxy = new CglibProxy(employeeService, txManager);
		//拿到代理对象
		EmployeeServiceImpl proxy = (EmployeeServiceImpl)cglibProxy.createProxy();
		proxy.save(user);
	}

工厂模式

工厂模式抽象于生活中的工厂,工厂的作用就是生产某种多种产品,工厂隐藏了(封装了)产品的复杂创建过程,以及可以实现生产功能复用的效果,让产品的生产更加方便和高效,而在Java语言中的工厂模式就是用来生成对象的。

工厂分为三种:简单工厂,工厂方法,抽象工厂。

该模式用于封装和管理对象的创建,是一种创建型模式。

1.简单工厂

该模式对对象创建管理方式最为简单,因为其仅仅简单的对不同类对象的创建进行了一层薄薄的封装。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。其UML类图如下:

简单工厂的优缺点

  • 优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。

  • 缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则,所有的类型都用了同一个工厂去创建,试想一下如果我要增加一个手机类型,那么我的创建实例的工厂需要重新修改。

2.工厂方法

和简单工厂模式中工厂负责生产所有产品相比,工厂方法模式将生成具体产品的任务分发给具体的产品工厂,即:每个类型都搞一个工厂,然后为每个工厂在抽象一个工厂。

举例:一个工厂又要创建小米,又要创建华为,本身比较混乱了,如果又新加了一个产品苹果,如果我在已有的工厂继续增加生成苹果的的业务,那么会把我的生成线搞得更乱,那我打算把产品分类,为每一种产品都建一个工厂,每个工厂只需要创建特定的某个产品即可,生产线变得有条理性。其UML类图如下:

3.抽象工厂

上面两种模式不管工厂怎么拆分抽象,都只是针对一类产品Phone(AbstractProduct),如果要生成另一种产品PC,应该怎么表示呢?

最简单的方式是把2中介绍的工厂方法模式完全复制一份,不过这次生产的是PC。但同时也就意味着我们要完全复制和修改Phone生产管理的所有代码,显然这是一个笨办法,并不利于扩展和维护。

抽象工厂模式通过在AbstarctFactory中增加创建产品的接口,并在具体子工厂中实现新加产品的创建,当然前提是子工厂支持生产该产品。否则继承的这个接口可以什么也不干。


结语

🔥如果文章对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下小老弟,蟹蟹大咖们~ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值