设计模式连环问,你能坚持到第几问?


前言

本文总结了常见的六种设计模式的基本概念,主要包括单例模式、代理模式、工厂模式等。当然在学习设计模式的时候不能只学习概念,还要看每种设计模式在Java中的实际应用场景,这样才会记忆深刻,所以本文也以举例的方式去展示每种设计模式在Java中的实际应用,加深读者的理解。


一、单例模式

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

作用:单例模式是保证系统实例唯一性的重要手段。用于一个全局类对象在多个地方被使用的场景下,保障了整个系统只有一个对象被使用,很好的节约了资源。

实现方法:将类的实例化方法私有化来防止程序通过其他方式创建该类的实例,提供一个全局唯一获取该类实例的方法帮助用户获取类的实例。

实现单例模式很简单,每次获取前先判断系统是否已经存在单例对象,没有就创建,有就返回这个对象。单例模式常见的写法有懒汉式单例和饿汉式单例还有双重校验锁。

之前的文章中已经详细介绍过这几种单例模式的写法,本文不再叙述,参考链接:
JAVA设计模式-单例模式


二、原型模式

实际上,Spring最基本的scope只有两种,即singleton和prototype。

  • singletonScope,即单例Bean,顾名思义具有单例模式的所有特性,在spring容器里面只会初始化出一个bean实例,存于缓存中。后续的请求都公用这个对象。最简单的创建单例bean的方式,就是直接在类名上面加@Service,@Controller这些注解。

  • prototypeScope,即原型Bean,每次请求时都会创建新的bean实例直接使用。创建原型Bean,需要显示指定scope属性。


三、代理模式

什么是代理模式:在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找保姆、找工作,买东西等都可以通过找中介完成。

代理模式的基本结构:

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

以58同城找保姆举例:我们要定义一个接口里面存放着做饭和洗衣服的业务方法(抽象主题),保姆(真实主题)需要实现这个接口(很容易理解.保姆需要会做饭和洗衣服),58同城(代理类)也需要实现这个接口,它可以增加一些额外的处理,如给保姆培训和接保姆等。我们访问时只需要访问代理类即可。

静态代理

静态代理就是按照代理模式书写的代码,其特点是代理类和目标类在代码中是确定的,因此称为静态。

特点:

1.代理类和目标类在代码中是确定的,因此称为静态,显然不够灵活。
2.如果被代理的目标对象非常多(例如58又要代理保姆对象,又要代理家教,又要代理水电工),则静态代理类就非常臃肿,难以胜任。
3.如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。

动态代理

动态代理中,代理类并不是在 Java 代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法、调用次数、添加日志功能等等,动态代理分为 jdk 动态代理和 cglib 动态代理

JDK代理

通过反射来实现的,借助 Java 自带的 java.lang.reflect.Proxy。

其步骤如下:

1. 编写一个委托类的接口,即静态代理的
2. 实现一个真正的委托类,即静态代理的
3. 创建一个动态代理类,实现 InvocationHandler 接口,并重写该 invoke方法
4. 在测试类中,生成动态代理的对象。 

动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务, 同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾 。

缺点:它无法摆脱仅支持 interface 代理的缺点,它们已经注定有一个共同的父类叫 Proxy。

实现:mybatis里面用的就是jdk代理,只写接口不用写实现类。原理如下:

1.mybatis的mapper接口的代理类在代理mapper接口的时候,代理对象会实现mapper接口

2.在执行代理对象的对应接口方法的时候,会去调用handler也就是mapperProxy的invoke方法,这个方法的三个参数分别是:代理对象本身,接口的方法对象,接口方法的参数列表。

3.在执行invoke方法的时候直接不执行接口的那个method,因为那个method没有任何实现,而去执行sqlSession里面的逻辑。sqlSession里面的执行逻辑才是mybatis的核心逻辑实现。

Cglib代理

JDK 实现动态代理需要实现类通过接口定义业务方法。

所以对于没有接口的类。就需要用到CGlib代理

CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类(父子类)并在子类中采用方法拦截的技术拦截所有父类方法的调用。但因为采用的是继承,所以不能对 final 修饰的类进行代理。

JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础,例如@Transactional事务这个注解底层就是用CGlib代理来实现的。

Cglib 子类代理实现方法:

1.需要引入 cglib 的 jar 文件,但是 Spring 的核心包中已经包括了 Cglib 功能,所以直接引入 spring-core-xxx.jar 即可. 
2.引入功能包后,就可以在内存中动态构建子类 
3.代理的类不能为 final,否则报错 
4.目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额 外的业务方法.

CGLIB 创建的动态代理对象比JDK 创建的动态代理对象的性能更高,但是 CGLIB创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。

同时由 于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的类无法进行代理。

Spring事务是怎么实现的?

  1. Spring事务底层是基于数据库事务和AOP机制的(CGlib代理)。
  2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean。
  3. 当调⽤代理对象的方法时,会先判断该方法上是否加了@Transactional注解。
  4. 如果加了,那么则利用事务管理器创建⼀个数据库连接。
  5. 并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交,这是Spring事务非常重要的⼀步。
  6. 然后执行当前方法,方法中会执行sql。
  7. 执行完当前方法后,如果没有出现异常就直接提交事务。
  8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务。
  9. Spring事务的隔离级别对应的就是数据库的隔离级别。
  10. Spring事务的传播机制是Spring事务自己实现的,也是Spring事务中最复杂的。
  11. Spring事务的传播机制是基于数据库连接来做的,一个数据库连接⼀个事务,如果传播机制配置为需要新开⼀个事务,那么实际上就是先建立一个数据库连接,在此新数据库连接上执行sql。

四、工厂模式

工厂模式概述:通俗的来讲,就是用工厂方法代替new操作创建一个实例化对象的方式。

实现方式:在接口中定义了创建对象的方法,而将具体的创建对象的过程在子类中实现。

简单工厂模式:如果要创建的产品不多,只要一个工厂类就可以完成。

抽象工厂模式:在简单工厂模式上添加了一个创建不同工厂的抽象接口(抽象类或接口实现),该接口可叫超级工厂。

Spring中的工厂模式

Spring 官方文档对 bean 的解释是: 在 Spring 中,构成应用程序主干并由Spring IOC容器管理的对象称为bean。bean是一个由Spring IOC容器实例化、组装和管理的对象

BeanFactory:是IOC容器的核心接口, 它定义了IOC的基本功能

BeanFactory是Spring 的“心脏”。它就是 SpringIOC 容器的真面目。Spring使用BeanFactory 来实例化、配置和管理 Bean。

缺点:BeanFactorty接口提供了配置框架及基本功能,但是无法支持spring的aop功能和web应用。

ApplicationContext: 应用上下文,继承了BeanFactory接口,拥有BeanFactory所有的功能,它是Spring的一个功能丰富的容器,提供了更多面向实际应用的功能。

如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了。

区别:BeanFactory主要是面对 spring 框架的基础设施,面对 spring 自己。Applicationcontex 主要面对与 spring 开发者。


五、装饰器模式

javaSE当中的IO中就有用到装饰器模式,例如缓冲流,它就是装饰模式的一种,它给普通的流套上一层更高级的管道。

缓冲流的特点就是令输入输出流具有1个缓冲区, 显著减少与外部设备的IO次数, 而且提供一些额外的方法。

同理,缓存输出流可以理解为:现在要往一个地方发一堆快递,如果一件一件或者拿麻袋发,效率非常低。可以把收到的快递暂时存到一个大卡车(数组缓冲区)上面,等到快递数量到达8192时一次性将快递发送过去(写出到目标文件中)。


六、责任链模式

拦截器是AOP和责任链模式的一个实现,拦截器栈就是将拦截器按一定的顺序连接成一条链,在访问被拦截的方法或者字段时,拦截器链中的拦截器就会按照之前定义的顺序进行调用。在此链式的执行过程中,任何一个拦截器都可以直接返回,从而终止余下的拦截器。

在这里插入图片描述


总结

本篇文章总结了六种常见的设计模式以及在Java体系中的实际应用,都是面试中非常容易被问到的知识点。同时,在学习设计模式的时候一定要结合具体的场景去学习,这样才会记忆深刻。


猜你喜欢

转载自blog.csdn.net/qq_52173163/article/details/126950664