1、装饰器模式:
可以在不改变原有对象的情况下,通过组合替代继承来扩展原始类的功能,应用在一些继承关系比较复杂的场景,比如IO场景。
例如字节缓冲流对字节流进行装饰,提高了它的性能。
2、适配器模式
主要用于接口互不兼容的类的协调工作,其中被适配的对象或者类称为 适配者(Adaptee) ,作用于适配者的对象或者类称为适配器(Adapter) 。适配器分为类适配器和对象适配器。类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
它也广泛应用于IO中,IO 流中的字符流和字节流的接口不同,我们可以通过适配器模式来协调他们之间的工作。将字节流对象适配成一个字符流对象,这样我们可以直接通过字节流对象来读取或者写入字符数据。
比如InputStream
和 OutputStream
的子类是被适者, InputStreamReader
和 OutputStreamWriter
是适配器。InputStreamReader
使用 StreamDecoder
(流解码器)对字节进行解码,实现字节流到字符流的转换, OutputStreamWriter
使用StreamEncoder
(流编码器)对字符进行编码,实现字符流到字节流的转换。
3、适配器模式和装饰器模式的区别
装饰器模式 更侧重于动态地增强原始类的功能,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。并且,装饰器模式支持对原始类嵌套使用多个装饰器。
适配器模式 更侧重于让接口不兼容而不能交互的类可以一起工作,当我们调用适配器对应的方法时,适配器内部会调用适配者类或者和适配类相关的类的方法,这个过程透明的。适配器和适配者两者不需要继承相同的抽象类或者实现相同的接口。
4、工厂模式
工厂模式用工厂类创建对象,不需要通过new的方式创建,从而将创建对象的具体过程屏蔽隔离起来,有着更高的灵活性,比如spring 框架的beanFactory,通过beanFactory来实例化我们的bean对象。
工厂模式可以分为三类:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
4.1、简单工厂模式
简单工厂模式通过工厂类来创建对象,将对象的创建和本身的业务逻辑分离,降低系统的耦合度,当以后需要改变时,只需要修改工厂类即可。
(在没有工厂的时代,如果客户需要一款宝马车,那么就需要客户去创建一款宝马车,然后拿来用。后来出现了工厂,用户不再需要去创建宝马车,由工厂进行创建,想要什么车,直接通过工厂创建就可以了。比如想要320i系列车,工厂就创建这个系列的车。)
- 工厂类角色: 该模式的核心,用来创建产品,含有一定的商业逻辑和判断逻辑
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
- 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
产品类:
abstract class BMW {
public BMW(){}
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("制造-->BMW320");
}
}
public class BMW523 extends BMW{
public BMW523(){
System.out.println("制造-->BMW523");
}
}
工厂类:
public class Factory {
public BMW createBMW(int type) {
switch (type) {
case 320:
return new BMW320();
case 523:
return new BMW523();
default:
break;
}
return null;
}
}
用户类:
public class Customer {
public static void main(String[] args) {
Factory factory = new Factory();
BMW bmw320 = factory.createBMW(320);
BMW bmw523 = factory.createBMW(523);
}
}
简单工厂模式的优缺点
优点:简单工厂模式提供专门的工厂类用于创建对象,客户端不需知道所创建的具体产品类的类名以及创建过程,只需知道具体产品类所对应的参数即可,将对象的创建和本身的业务逻辑分离,在一定程度上提高了系统的灵活性。
缺点: 不符合“开闭原则”,每次添加新产品就需要修改工厂类。
为了解决简单工厂模式的问题,出现了工厂方法模式。
补充知识:静态工厂
静态工厂模式:在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的
4.2 工厂方法模式:
工厂方法模式将工厂抽象化,定义抽象工厂,并提供具体实现。每增加新产品,只需增加该产品以及对应的具体实现工厂类,符合“开闭原则”,扩展时不必去修改原来的代码。当我们选择哪个具体工厂时,就已经决定了实际创建的产品是哪个了。
- 抽象工厂 AbstractFactory: 工厂方法模式的核心,是具体工厂角色必须实现的接口或者必须继承的父类,在 Java 中它由抽象类或者接口来实现。
- 具体工厂 Factory:被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码
- 抽象产品 AbstractProduct:是具体产品继承的父类或实现的接口,在 Java 中一般有抽象类或者接口来实现。
- 具体产品 Product:具体工厂角色所创建的对象就是此角色的实例。
产品类:
abstract class BMW {
public BMW(){}
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("制造-->BMW320");
}
}
public class BMW523 extends BMW{
public BMW523(){
System.out.println("制造-->BMW523");
}
}
工厂接口:
interface FactoryBMW {
BMW createBMW();
}
工厂实现类:
public class FactoryBMW320 implements FactoryBMW{
@Override
public BMW320 createBMW() {
return new BMW320();
}
}
public class FactoryBMW523 implements FactoryBMW {
@Override
public BMW523 createBMW() {
return new BMW523();
}
}
用户类:
public class Customer {
public static void main(String[] args) {
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
BMW320 bmw320 = factoryBMW320.createBMW();
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
BMW523 bmw523 = factoryBMW523.createBMW();
}
}
工厂模式优缺点:
优点:工厂方法模式将工厂抽象化,定义抽象工厂,并提供具体实现。每增加新产品,只需增加该产品以及对应的具体实现工厂类,符合“开闭原则”,扩展时不必去修改原来的代码。当我们选择哪个具体工厂时,就已经决定了实际创建的产品是哪个了。
缺点:每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
4.3抽象工厂模式:
前沿:
在工厂方法模式中,我们使用一个工厂创建一个产品,一个具体工厂对应一个具体产品,但有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。
在介绍抽象工厂模式前,我们先厘清两个概念:
- 产品等级结构:产品等级结构指的是产品的继承结构,例如一个空调抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个空调抽象类和他的子类就构成了一个产品等级结构。
- 产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产海尔空调、海尔冰箱,那么海尔空调则位于空调产品族中。
产品等级结构和产品族结构示意图如下:
抽象工厂模式主要用于创建相关对象的家族。当需要用到一个产品族中的物品时,通过抽象工厂模式,使客户端始终只使用同一个产品族中的对象;并且客户端不需要明确制定实现类,交给对应的工厂进行。
缺点:添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类。
- 抽象工厂 AbstractFactory:定义了一个接口,这个接口包含了一组方法用来生产产品,所有的具体工厂都必须实现此接口。
- 具体工厂 ConcreteFactory:用于生产不同产品族,要创建一个产品,用户只需使用其中一个工厂进行获取,完全不需要实例化任何产品对象。
- 抽象产品 AbstractProduct:这是一个产品家族,每一个具体工厂都能够生产一整组产品。
- 具体产品 Product:实现抽象类
通过抽象工厂模式,我们可以实现以下的效果:比如宝马320系列使用空调型号A和发动机型号A,而宝马230系列使用空调型号B和发动机型号B,在为320系列生产相关配件时,就无需制定配件的型号,它会自动根据车型生产对应的配件型号A。
也就是说,当每个抽象产品都有多于一个的具体子类的时候(空调有型号A和B两种,发动机也有型号A和B两种),工厂角色怎么知道实例化哪一个子类呢?抽象工厂模式提供两个具体工厂角色(宝马320系列工厂和宝马230系列工厂),分别对应于这两个具体产品角色,每一个具体工厂角色只负责某一个产品角色的实例化,每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例。
产品类:
//发动机以及型号
public interface Engine {}
public class EngineA implements Engine{
public EngineA(){
System.out.println("制造-->EngineA");
}
}
public class EngineB implements Engine{
public EngineB(){
System.out.println("制造-->EngineB");
}
}
//空调以及型号
public interface Aircondition {}
public class AirconditionA implements Aircondition{
public AirconditionA(){
System.out.println("制造-->AirconditionA");
}
}
public class AirconditionB implements Aircondition{
public AirconditionB(){
System.out.println("制造-->AirconditionB");
}
}
抽象工厂接口:
//创建工厂的接口
public interface AbstractFactory {
//制造发动机
public Engine createEngine();
//制造空调
public Aircondition createAircondition();
}
创建工厂类:
//为宝马320系列生产配件
public class FactoryBMW320 implements AbstractFactory{
@Override
public Engine createEngine() {
return new EngineA();
}
@Override
public Aircondition createAircondition() {
return new AirconditionA();
}
}
//宝马523系列
public class FactoryBMW523 implements AbstractFactory {
@Override
public Engine createEngine() {
return new EngineB();
}
@Override
public Aircondition createAircondition() {
return new AirconditionB();
}
}
客户:
public class Customer {
public static void main(String[] args){
//生产宝马320系列配件
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();
//生产宝马523系列配件
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
factoryBMW523.createEngine();
factoryBMW523.createAircondition();
}
}
(参考链接:https://blog.csdn.net/a745233700/article/details/120253639)
5、观察者模式
观察者模式又称为 发布-订阅模式,定义了对象之间一对多依赖关系,当被观察者的状态发生改变时,它的所有观察者都会收到通知。这些观察者之间没有相互联系,所以能够根据需要增加和删除观察者,使得系统更易于扩展,符合开闭原则。并且彼此不需要清楚对方的细节,也可以交互。
缺点:如果存在很多个被观察者的话,那么将需要花费一定时间通知所有的观察者,如果观察者与被观察者之间存在循环依赖的话,那么可能导致系统崩溃,并且观察者仅仅只是知道观察目标发生了变化,不知道它是怎么发生变化的。
情景:在气象观测站中,它能够追踪目前的天气状况,包括温度、适度、气压。需要实现一个布告板,能够分别显示目前的状态,气象统计和简单的预报。当气象站中获取最新的测量数据时,三种布告板必须实时更新。
- Subject:抽象主题(被观察者),每一个主题可以有多个观察者,并将所有观察者对象的引用保存在一个集合里,被观察者提供一个接口,可以增加和删除观察者角色
- ConcreteSubject:具体主题,将有关状态存入具体观察者对象,在主题发生改变时,给所有的观察者发出通知
- Observer:抽象观察者,为所有的具体观察者定义一个更新接口,该接口的作用是在收到主题的通知时能够及时的更新自己
- ConcreteObserver:具体观察者,实现抽象观察者角色定义的更新接口,以便使本身的状态与主题状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
主题接口:
public interface Subject {
/**
* 注册观察者
* @param observer
*/
public void registerObserver(Observer observer);
/**
* 删除观察者
* @param observer
*/
public void removeOberver(Observer observer);
/**
* 当主题状态发生改变时,这个方法需要被调用,以通知所有观察者
*/
public void notifyObserver();
}
观察者接口:
public interface Observer {
public void update(float temp,float humidity,float pressure);
}
布告板显示接口
public interface DisplayElement {
public void display();
}
WeatherData实现主题接口:
public class WeatherData implements Subject{
private List<Observer> observers;
private float tempterature;
private float pressure;
private float humidity;
public WeatherData(){
observers = new ArrayList<Observer>();
}
@Override
public void notifyObserver() {
for(int i = 0; i < observers.size();i++){
Observer observer = observers.get(i);
observer.update(tempterature, humidity, pressure);
}
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeOberver(Observer observer) {
int i = observers.indexOf(observer);
if(i >= 0){
observers.remove(i);
}
}
/**
* 气象站得到更新的观测数据时,通知观察者
*/
public void measurementChanged(){
notifyObserver();
}
public void setMeasurements(float temperature,float humidity,float pressure){
this.tempterature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementChanged();
}
}
布告板
public class CurrentConditionsDisplay implements Observer,DisplayElement{
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this); //注册观察者
}
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println("Current conditions:"+temperature+"F degrees and "+humidity+"% humidity");
}
}
测试程序:
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay conditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 78, 40.4f);
}
}
观察者模式的两种模式:
(1)拉取模式:目标角色在发生变化后,仅仅告诉观察者角色“我变化了”,观察者角色如果想要知道具体的变化细节,则就要自己从目标角色的接口中得到,这种模式称为拉取模式,就是说变化的信息是观察者角色主动从目标角中“拉”出来的。
(2)推送模式:目标角色发生变化时,通知观察者的同时,通过参数将变化的细节传递到观察者角色中去。
这两种模式的使用取决于系统设计,如果目标角色比较复杂,并且观察者角色进行更新时必须得到一些具体变化的信息,则“推模式”比较合适,如果目标角色比较简单,则“拉模式”就很合适啦。
代理模式:
我们使用代理对象来代替对真实对象(real object)的访问,这样就可以提供额外的功能操作,扩展目标对象的功能,某个方法执行前后增加一些自定义的操作。
静态代理:
我们对目标对象的每个方法的增强都是手动完成的, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。如果需要新增功能的话,就必须重新修改,非常麻烦。
动态代理:
代理类并不是在Java代码中定义,而是在运行时根据我们在动态生成的。这样就可以很方便的对代理类的函数进行统一处理,而不用修改每个代理类中的方法。
动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理
JDK 动态代理
在 Java 动态代理机制中 InvocationHandler
接口和 Proxy
类是核心。
3.1.2. JDK 动态代理类使用步骤
- 定义一个接口及其实现类;
- 自定义类实现
InvocationHandler
并重写invoke
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象
invoke()
方法有下面三个参数:
- proxy :动态生成的代理类
- method : 与代理类对象调用的方法相对应
- args : 当前 method 方法的参数
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
public class ProxyTest {
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理执行上交班费的方法
stuProxy.giveMoney();
}
}
Proxy.newProxyInstance
这个方法一共有 3 个参数:
- loader :类加载器,用于加载代理对象。
- interfaces : 被代理类实现的一些接口;
- h : 实现了
InvocationHandler
接口的对象;
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
这样,所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
CGLIB 动态代理
JDK 动态代理只能代理实现了接口的类,为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
CGLIB 动态代理类使用步骤
- 定义一个类;
- 自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; - 通过
Enhancer
类的create()
创建代理类;
不同于 JDK 动态代理不需要额外的依赖。CGLIBopen in new window(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
需要自定义类实现 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
- obj : 被代理的对象(需要增强的对象)
- method : 被拦截的方法(需要增强的方法)
- args : 方法入参
- proxy : 用于调用原始方法
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类,来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy 去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。
模板方法:
它定义一个操作的模板结构,而将具体内容延迟到子类中实现。 在不改变模板结构的前提下在子类中重新定义模板中的内容。
- 提高代码复用性
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中 - 实现了反向控制
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 & 符合“开闭原则”
Spring 中 JdbcTemplate
、transactionTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
单例模式:
是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
- 懒汉式:在真正需要使用对象时才去创建该单例类对象
- 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
public class LazySingleton{
//单例对象声明
private static LazySingleton lazySingleton = null;
//私有化构造器
private LazySingleton(){
}
//获取单例对象,当调用该方法时才去判断是否存在对象,不存在实例化一个对象
public static synchronized LazySingleton getSingletonInstance(){ //需要加锁才能保证线程安全
if(lazySingleton == null){
return new LazySingleton();
}
return lazySingleton;
}
}
public class HungrySingleton{
//私有化构造器
private HungrySingleton(){
}
//单例对象实例化,在类初始化的时候就实例化该对象
private static HungrySingleton hungrySingleton = getSingletonInstance();
//获取单例对象
public static HungrySingleton getSingletonInstance(){
if(hungrySingleton == null){
return new HungrySingleton();
}
return hungrySingleton;
}
}
对比懒汉式和饿汉式,懒汉式需要加锁才能保证线程安全,当多个线程调用此方法时会造成低效率;饿汉式类初始化的时候便进行对象创建,当此对象还没需要被用到时,则会造成资源浪费。因此引申出来第三种单例模式的实现,即双重校验锁。
双重检查锁定(Double-Checked Locking):
在懒汉式的基础上添加双重检查,确保只在第一次实例化时加锁,提高了并发性能。
public class DoubleCheckedLockingSingleton {
private static volatile DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton() {
// 私有构造方法,防止外部实例化
}
public static DoubleCheckedLockingSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLockingSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
应用场景:
Spring 默认情况下将所有的 Bean 配置为单例(Singleton)作用域,这意味着在整个应用程序中只会创建一个 Bean 实例。这种单例模式的应用有助于减少资源消耗、提高性能,并确保对 Bean 的共享和一致性。