简介:
本期文章将汇总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中增加创建产品的接口,并在具体子工厂中实现新加产品的创建,当然前提是子工厂支持生产该产品。否则继承的这个接口可以什么也不干。
结语
🔥如果文章对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下小老弟,蟹蟹大咖们~