DesignPattern设计模式
1 前言
1.1 内容概要
- 理解使用设计模式的目的
- 掌握软件设计的SOLID原则
- 理解单例的思想,并且能够设计一个单例
- 理解工厂设计模式的思想,能够设计简单工厂,理解工厂方法
- 了解建造者模式的思想,掌握其代码风格
- 理解代理设计模式的思想,理解动态代理类和委托类(目标类)之间的关系,熟悉代理对象调用方法的执行过程,熟练使用动态代理完成代码设计
- 了解责任链设计模式的思想
1.2 前置准备
理解抽象部分:为什么使用接口或抽象类,在什么情况下使用接口,什么情况下使用抽象类
静态内部类的类加载过程
匿名内部类的定义和使用
反射:反射过程中的常用名词Class、Field、Method、Parameter、Constructor。反射过程中的常用方法
- 获得class、field、method、parameter、constructor → getDeclaredXXX
- 通过反射获得实例 class和constructor
- 通过反射调用方法 method.invoke(instance,args)
Lombok使用
2 设计模式简介
2.1 什么是设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式代表了最佳的实践,通常被有经验的软件开发人员所使用。
2.2 设计模式的分类
设计模式共有23种,根据用途的不同,设计模式可以分为:创建型、结构型、行为型三种。
-
创建型模式
这类模式提供创建对象的机制, 能够提升已有代码的灵活性和可复用性。
包括:工厂方法(Factory Method)、抽象工厂(Abstract Factory)、生成器(Builder)、单例(Singleton)、原型(prototype)。常见的有单例、工厂、建造者(生成器)模式 -
结构型模式
这类模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。包括:适配器(Adapter)、桥接(Bridge)、代理(Proxy)、装饰(Decorator)、外观(Facade)、享元(Flyweight)、组合(Composite)。常用的有代理、桥接、装饰者、适配器模式。
-
行为型模式
这类模式负责对象间的高效沟通和职责委派。包括:责任链(Chain of Responsibility)、迭代器(Iterator)、状态(State)、观察者(Observer)、策略(Strategy)、模板方法(Template Method)、命令(Command)、中介者(Mediator)、备忘录(Memento)、Visitor(访问者)。常见的有观察者、模板、策略、责任链、迭代器、状态模式。
2.3 设计模式原则
设计模式需要有设计的原则作为指导纲领,设计模式是在设计原则的指引下设计出来的。因为我们需要对设计原则有一个清晰的认识。
设计原则按照字母手写简写可以概括为SOLID
原则。
单一职责原则(Single Responsibility Principle)
开放封闭原则(Open Close Principle)
里氏替换原则(Liskov Substitution Principle)
迪米特法则(Least Knowledge Principle)
接口分离原则(Interface Segregation Principle)
依赖倒置原则(Dependency Inversion Principle)
2.3.1 单一职责原则
尽量使得每个类只负责整个软件的功能模块中的一个。当程序不断壮大之后,类也会变得非常的庞杂,查找某部分代码也会变得非常的吃力;如果此时需要做任何一处的修改,那么整个类的代码都会受到影响。
2.3.2 开放封闭原则
开闭原则规定软件设计中的对象、类、模块以及函数等对于扩展是开放的,但是对于修改是封闭的。如果一个功能模块已经开发、测试完毕,那么对其代码直接进行修改便是由很大风险的。如果有新的业务功能,那么应当做的事情是对于现有代码进一步扩展,而不是修改现有代码。比如可以创建一个子类来重写这部分业务逻辑以到达目的等。也就是说,我们应该选择使用抽象来定义结构,用具体实现来扩展细节。
上述讨论的前提是代码中没有缺陷等问题,如果代码中存在缺陷、bug等,那么直接对其进行修复即可。
2.3.3 里氏替换原则
它由芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次会议上名为“数据的抽象与层次”的演说中首先提出。主要内容如下:
如果S是T的子类型,对于S类型的任意对象,如果将他们看作是T类型的对象,则它的行为也理应和预期的行为一致。
这意味着子类必须保持与父类行为的兼容。在重写一个方法时,你要对基类行为进行扩展,而不是将其完全替换。在使用父类的程序中,替换为使用子类,那么程序的运行结果应该是一致的,不会发生任何异常。
2.3.4 迪米特法则
又叫作最少知道原则,指的是一个类/模块对于其他的类/模块有越少的了解越好。简单来说就是不应该有依赖关系的类之间,不要存在依赖关系;有依赖关系的类之间,尽量只依赖于接口。
2.3.5 接口隔离原则
一个类对另外一个类的依赖应当建立在最小的接口上。这句话的含义其实是要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
2.3.6 依赖倒置原则
指的是设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。其所表达的含义是指在软件设计过程中,细节具有多变性,而抽象则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构稳定的多。
3 常用设计模式
3.1 创建型模式
创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。
3.1.1 单例模式(Singleton)
单例是一种创建型设计模式,让你保证一个类只有一个实例对象,并提供了一个访问该实例对象的全局节点 。
3.1.1.1 饿汉模式
在类加载的过程中初始化了私有的静态实例对象,保证了该实例对象的线程安全性。因为该实例对象先于使用前提供,所以称之为饿汉模式。
package com.cskaoyan.pattern.singleton;/*** @ClassName Singleton1* @Description:* 饿汉式**/
public class Singleton1 {//创建私有静态实例对象private static final Singleton1 instance = new Singleton1();//私有化构造函数private Singleton1(){}public static Singleton1 getInstance(){return instance;}
}
饿汉式特点:不支持延时加载(懒加载),获取对象速度比较快;但是如果对象比较大,或者一直没有去使用,那么比较浪费内存空间。
3.1.1.2 懒汉模式(线程不安全)
package com.cskaoyan.pattern.singleton;/*** @ClassName Singleton2* @Description:* 懒汉式-线程不安全**/
public class Singleton2 {private static Singleton2 instance;//私有化构造函数private Singleton2(){}//判断当前对象是否已经被创建public static Singleton2 getInstance(){if(instance == null){instance = new Singleton2();}return instance;}
}
编写测试用例,我们使用1000
个线程来创建Singleton2
对象,发现对象的地址不同。产生上述问题的主要原因在于我们执行的代码其实都不是原子性,在多线程操作的过程中,会进行线程切换。比如线程A执行到 if(instance == null){
,继续执行代码便会创建instance实例对象, 但是此时进行了线程切换;切换到了线程B,线程B创建了instance对象;随后再次线程切换给线程A,因为线程A已经执行过判断,所以便会直接执行instance = new Singleton2();
,便又会创建一个对象。
3.1.1.3 懒汉模式(线程安全)
如何保证线程安全呢?使用synchronized
关键字即可。
package com.cskaoyan.pattern.singleton;/*** @ClassName Singleton3* @Description:* 懒汉式-线程安全**/
public class Singleton3 {private static Singleton3 instance;//私有化构造函数private Singleton3(){}//引入synchronized,保证多线程模式下实例对象的唯一性public static synchronized Singleton3 getInstance(){if(instance == null){instance = new Singleton3();}return instance;}
}
使用synchronized锁住对象创建的方法,防止该方法被多个线程同时调用。
但是这种方式最合适吗?
因为我们对getInstance()
添加了锁,降低了该方法的并发量;实际上,我们只需要针对最开始抢先创建实例对象的线程加锁即可,后续的线程在执行时,因为if(instance == null)
条件已经不满足,所以直接执行返回实例对象即可,此时不需要加锁。
3.1.1.4 双重检查(Double Check)
针对上述存在的问题,我们做了进一步修改。这种方式最明显的特征是synchronized
关键字不是修饰整个方法,而是仅修饰创建对象的代码块,可以提高并发;此外,存在两个if条件判断语句。这也是双重检查的由来。为什么需要双重检查呢?线程A可能首先先执行到了外层的if条件判断,执行通过之后并没有进一步执行,而是进行了线程的切换,切换成了线程B;线程B也执行了外层的if
条件判断,并且顺利地获取到了锁,执行完了内部的if
条件判断,创建了实例对象;如果此时线程再次切换给线程A,线程A因为刚刚已经执行完外层的if条件判断,此时顺利的获取到了锁,如果没有内部的if
条件判断,则会再次创建一个实例对象。这也是为何一定要双重检查的原因。
package com.cskaoyan.pattern.singleton;/**
* @ClassName Singleton4
* @Description:
* 懒汉式-双重检查
**/
public class Singleton4 {private static Singleton4 instance;//私有化构造函数private Singleton4(){}public static Singleton4 getInstance(){if(instance == null){synchronized (Singleton4.class){if(instance == null){instance = new Singleton4();}}}return instance;}
}
3.1.1.5 静态内部类
利用静态内部类来解决延迟加载、线程安全的问题;并且可以使得代码更加简洁。由JVM
来保障线程安全性。
public class Singleton5 {private static Singleton5 instance;//私有化构造函数private Singleton5(){}//静态内部类private static class SingletonHolder{private static Singleton5 instance = new Singleton5();}public static Singleton5 getInstance(){return SingletonHolder.instance;}
}
3.1.1.6 枚举
public enum Singleton6 {INSTANCE;public static Singleton6 getInstance(){return INSTANCE;}
}
3.1.1.7 总结
- 饿汉式:在类加载时期,便已经将instance实例对象创建了;所以这种方式是线程安全的方式,但是不支持懒加载。
- 懒汉式:该种方式支持懒加载,但是要么不是线程安全,要么虽然是线程安全,但是需要频繁释放锁、抢夺锁,并发量较低。
- 双重检查:既可以实现懒加载,又可以实现高并发的需求。这种方式比较完美,但是代码有一些复杂。
- 静态内部类:使用该种方式也可以解决懒加载以及高并发的问题,代码实现起来比双重检查也是比较简洁。
- 枚举:最简单、最完美的实现方式。
3.1.2 工厂模式(Factory)
工厂顾名思义就是生产产品的地方。我们通常会定义工厂(类、接口),通过该工厂类(或其工厂实例)提供的方法能够获得返回值,该返回值就是通过工厂生产的实例。
也就是说,工厂中一定会提供一个返回实例的方法。其中核心的好处是封装(隐藏)生产的具体细节
工厂类或接口的命名方式,通常为XXXFactory
3.1.2.1 简单工厂模式
之所以叫简单工厂是因为真的非常简单,只要一个工厂(函数)就可以了,,那么只需要传入不同的参数,就可以返回不同的产品(实例),这种模式就叫简单工厂模式。
比如,我们要生产Tesla汽车,Tesla下又有不同的产品,比如Model3、ModelY、ModelS等,我们其实可以通过给简单工厂传入不同的参数,来生产不同的产品
首先定义不同的产品
public abstract class Tesla {String name;public Tesla(String name) {this.name = name;}public void run(){System.out.println(name + "在路上跑");}
}
public class Model3 extends Tesla{public Model3() {super("model 3");}
}
public class ModelS extends Tesla{public ModelS() {super("model S");}
}
public class ModelY extends Tesla{public ModelY() {super("model Y");}
}
我们在上面定义了抽象类Tesla,通过不同的子类定义不同的车型
接下来,我们首先先不使用工厂
Scanner scanner = new Scanner(System.in);
String keyword = scanner.nextLine();
Tesla tesla = null;
switch (keyword) {case "model3":tesla = new Model3();break;case "modely":tesla = new ModelY();break;case "models":tesla = new ModelS();break;default:tesla = new Tesla("未知车辆") {@Overridepublic void run() {System.out.println(name + "路上请注意,道路千万条,安全第一条");}};break;
}
tesla.run();
然后我们再使用工厂
将获得tesla对象的过程放入到工厂的生产方法中,故定义一个这样的工厂
public class SimpleTeslaFactory {public static Tesla create(String keyword) {Tesla tesla = null;switch (keyword) {case "model3":tesla = new Model3();break;case "modely":tesla = new ModelY();break;case "models":tesla = new ModelS();break;default:tesla = new Tesla("未知车辆") {@Overridepublic void run() {System.out.println(name + "路上请注意,道路千万条,安全第一条");}};break;}return tesla;}
}
其中的create方法可以定义为静态
我们通过工厂提供的create方法可以直接获得tesla对象
Scanner scanner = new Scanner(System.in);
String keyword = scanner.nextLine();
Tesla tesla = SimpleTeslaFactory.create(keyword);
tesla.run();
3.1.2.2 工厂方法模式
工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
我们模拟客户下单一辆特斯拉电动车,特斯拉生产车间需要交付一辆该型号汽车来讲述该设计模式。首先不借助于任何设计模式,我们先完成该功能。
import java.util.Scanner;/*** @ClassName OrderCar**/
public class OrderCar {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);String keyword = scanner.nextLine();Tesla tesla = SimpleTeslaFactory.create(keyword);tesla.run();}
}
上述的案例,我们便实现了根据订购的车型不同,生产不同的车辆。但是面临最大的问题便是在于扩展车型时非常的麻烦。当我们需要重构代码时,代码中涉及到的地方千丝万缕、错综复杂会使得开发人员望而却步。接下来,我们尝试使用工厂方法来重构上述代码。
对应的类关系如下
public interface TeslaFactory {public Tesla getTesla();
}
public class ModelYFactory implements TeslaFactory{@Overridepublic Tesla getTesla() {return new ModelY();}
}
public class Model3Factory implements TeslaFactory{@Overridepublic Tesla getTesla() {return new Model3();}
}
//其他工厂类似,就不全部列举了
public class OrderTesla {private static Map<String, TeslaFactory> factoryMap = new HashMap<>();static {factoryMap.put("modelx", new ModelXFactory());factoryMap.put("modely", new ModelYFactory());factoryMap.put("models", new ModelSFactory());factoryMap.put("model3", new Model3Factory());}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);String keywords = scanner.nextLine();TeslaFactory teslaFactory = factoryMap.get(keywords.toLowerCase());Tesla tesla = teslaFactory.getTesla();tesla.run();}
}
通过OrderTesla
类的代码和OrderCar
代码进行比较,我们发现代码明显变得更加简洁了。
3.1.2.3 抽象工厂模式
抽象工厂模式是所有工厂模式中抽象程度最高的一种模式。抽象工厂模式可以向客户端提供一个接口,使得客户端可以在不必指定具体类型的情况下,能够创建多个一系列或者相关联的对象。
工厂方法和抽象工厂,首先都是要将工厂抽象为接口或抽象类。工厂方法主要是生产的是单个产品,抽象工厂主要是生产是一系列的产品。
工厂方法:单品
抽象工厂:产品矩阵
我们以下面这个案例来进行讲述。随着智能家居的兴起,许多家庭在选择家居电器时,会倾向于选择同一厂家的产品。比如目前市面上有Haier
以及Mi
的家居产品。不同的厂家产品线都非常丰富,涵盖TV
、Freezer
等。设计一套代码程序,根据用户选择的厂家,提供对应的配套产品。
对应的类关系如下:
public abstract class AbstractFurnitureFactory {public abstract TV createTV();public abstract Freezer createFreezer();
}
public class MiFurnitureFactory extends AbstractFurnitureFactory{@Overridepublic TV createTV() {return new MiTV();}@Overridepublic Freezer createFreezer() {return new MiFreezer();}
}
public class HaierFurnitureFactory extends AbstractFurnitureFactory{@Overridepublic TV createTV() {return new HaierTV();}@Overridepublic Freezer createFreezer() {return new HaierFreezer();}
}
public class OrderFurniture {public static void main(String[] args) {MiFurnitureFactory miFactory = new MiFurnitureFactory();TV tv = miFactory.createTV();Freezer freezer = miFactory.createFreezer();System.out.println("tv instanceof MiTV = " + (tv instanceof MiTV));System.out.println("freezer instanceof MiFreezer = " + (freezer instanceof MiFreezer));}
}
3.1.3 建造者模式(Builder)
建造者模式也叫作生成器模式,就是分步骤创建复杂对象,该模式允许使用相同的创建代码生成不同类型和形式的对象。
在开发中,有时候我们需要创建出一个很复杂的对象,这个对象的创建有一个固定的步骤,并且每个步骤中会涉及到多个组件对象,这个时候就可以考虑使用建造者模式。使用建造者模式将原本复杂的对象创建过程按照规律将其分解成多个小步骤,这样在构建对象时可以灵活的选择或修改步骤。建造者模式将对象的创建和表示过程进行分离,这样我们可以使用同样的过程,只需修改这个过程中的小步骤,便能够构建出不同的对象。而对于调用方来说,我们只需要传入需要构建的类型,便能够得到需要的对象,并不需要关系创建的过程,从而实现解耦。
比如我们要制造手机,手机里包含屏幕、颜色、电池、摄像头、系统等组成,那么我们定义一个Phone如下
@Data
public class Phone {private String battery;private String screen;private String os;private String camera;private String color;// 通过@Data提供了getter/setter方法,以及我们打印的时候用的toString方法
}
然后我们要提供一个PhoneBuilder类
- 通过Builder类提供的build方法能够获得Phone实例
- 同时提供一些方法,通过这些方法能够设置build方法获得的该phone实例的属性值
- 要保证这些方法操作的是同一个phone实例,要在Builder中提供phone成员变量
基于以上,我们定义的PhoneBuilder如下
public class PhoneBuilder {private Phone phone = new Phone();public PhoneBuilder color(String color) {this.phone.setColor(color);return this;}public PhoneBuilder battery(String battery) {this.phone.setBattery(battery);return this;}public PhoneBuilder screen(String screen) {this.phone.setScreen(screen);return this;}public PhoneBuilder os(String os) {this.phone.setOs(os);return this;}public PhoneBuilder camera(String camera) {this.phone.setCamera(camera);return this;}public Phone build() {return this.phone;}
}
使用如下
public class UseBuilder {public static void main(String[] args) {PhoneBuilder builder = new PhoneBuilder();Phone phone = builder.battery("4000毫安大容量").camera("徕卡顶级镜头").color("尊贵黑").screen("2K高清分辨率").os("Android").build();System.out.println("phone = " + phone);}
}
上述代码是建造者模式最经典的使用方式。
建造者模式的优点如下:
- 可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
- 不同的构建器,相同的装配,也可以做出不同的对象,实现了更好的复用。
- 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
采用链式调用,一步一步把一个完整的对象构建出来。使用该模式是一次性将一个完整的对象构建出来,更加的紧凑,同时也避免了对象在其他处调用了set方法导致属性赋值错误。
建造者设计模式和工厂设计模式都是为了创建具体的实例:工厂模式更关注通过什么工厂生产什么实例,建造者模式主要是通过组装零配件而产生一个新产品
3.2 结构型模式
3.2.1 代理(Proxy)
代理是一种结构型设计模式,可以允许我们生成对象的替代品。代理控制着对于原对象的访问,同时也允许在原对象的方法之前前后做一些处理,便可以实现在原方法执行前后都会执行某段代码逻辑的功能。这个也是面向切面编程的指导思想。
代理模式在软件开发过程中的应用场景也非常常见。在客户端以及客户端访问的目标类对象中间,额外再引入一个第三方代理类对象。如果直接访问目标类对象,就是执行对应的方法;如果客户端访问的是代理类对象,那么不仅可以访问对应的方法,还会再方法的执行前后执行对应的前置、后置通知。
3.2.1.1 静态代理
public interface UserService {void insert();
}
public class UserServiceImpl implements UserService {@Overridepublic void insert() {System.out.println("目标类执行了insert方法");}
}
public class UserServiceProxy implements UserService {UserService target;public UserServiceProxy(UserService target) {//注入委托类对象this.target = target;}@Overridepublic void insert() {System.out.println("代理之前打印一个日志");target.insert();System.out.println("代理之后打印一个日志");}
}
@Testpublic void test1(){UserServiceProxy proxy = new UserServiceProxy(new UserServiceImpl());proxy.insert();}
代理模式最大的优点在于可以不更改目标类代码的前提下,扩展目标类代码的功能。
静态代理最大的缺点在于代码较为冗余,每代理一个类,便要手动编写一个代理类;代理对象和目标类对象均实现了接口,如果接口发生了修改,不仅目标类需要更改,代理类也需要同步发生修改,维护成本变高了很多。
因此,我们希望可以在程序运行过程中,动态地生成一个代理类对象,这样处理任务更加的方便。这也便是我们接下来介绍的动态代理。
3.2.1.2 JDK动态代理
静态代理,顾名思义,便是在编译时,就已经实际存在了该class
文件;而动态代理,在编译时期,实际上并不存在该class
文件,而是程序在运行阶段动态生成了字节码。JDK
动态代理,即JDK
给我们提供的动态生成代理类的方式,无需引入第三方jar
包,但是使用JDK
动态代理有一个先决条件,那就是目标类对象必须实现了某个接口;如果目标类对象没有实现任何接口,则JDK
动态代理无法使用。
如果使用JDK
提供的动态代理,那么需要借助于如下几个类
-
java.lang.reflect.Proxy
API 参数 返回值 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) loader表示目标类使用的类加载器;interfaces表示目标类所实现的接口类型;h表示处理器,用来规定代理的内部细节 返回一个实现指定接口的代理类实例对象;代理类对象和目标类对象实现相同的接口类型 -
java.lang.reflect.InvocationHandler
API 参数 返回值 public Object invoke(Object proxy, Method method, Object[] args) proxy表示JDK帮助开发者生成的代理类对象,这个参数一般不用理会;method表示的是目标类中的方法;args表示执行目标类方法时传递的参数;三个参数合在一起表示的含义表示代理类如何来代理、增强目标类里面的方法 代理类执行完对应的方法时它的返回值
public class ProxyFactory {Object target;public ProxyFactory(Object target) {this.target = target;}public Object newProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {//代理类如何代理@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理之前签订合约");Object invoke = method.invoke(target, args);System.out.println("代理完毕转账确认");return invoke;}});}
}
@Testpublic void test2(){UserService userService = new UserServiceImpl();//对哪个目标类进行代理,我们对UserServiceImpl进行代理//生成代理类对象UserService userServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {//代理类如何代理@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理之前签订合约");Object invoke = method.invoke(userService, args);System.out.println("代理完毕转账确认");return invoke;}});;//代理类对象执行insert方法,什么逻辑呢?主要是invoke里面的代码逻辑userServiceProxy.insert();}
利用线上监测工具以及反编译工具,可以看到生成的代理类对象源码
public final class $Proxy0
extends Proxy
implements UserService {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.cskaoyan.pattern.proxy.UserService").getMethod("insert", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}//主要关注insert方法public final void insert() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}
3.2.1.3 Cglib动态代理
Cglib(Code Generation Library
)是一个开源项目,是一个强大的,高性能,高质量的Code
生成类库,它可以在运行期扩展Java
类与实现Java接口。我们可以借助于Cglib
来帮助我们动态地生成代理类对象。Cglib
可以弥补JDK
动态代理的不足,JDK
要求目标类必须实现了某个接口,才可以执行代理功能;而Cglib
对此无任何要求,主要原因在于Cglib
扩展的代理类会继承自目标类。所以这也要求我们的目标类不能是final
修饰。
使用Cglib
涉及到的相关类如下
-
net.sf.cglib.proxy.Enhancer
API 参数 返回值/说明 enhancer.setSuperclass(superClass) 父类的字节码对象,也就是我们的目标类 无返回值;Cglib产生的代理类会继承目标类,所以此处设置的父类也就是目标类 enhancer.setCallBack(callback) 设置一个回调函数,代理类对象如何代理目标对象需要在回调函数中制定策略 CallBack是一个接口,MethodInterceptor是一个子接口。我们选用该类来设置回调策略 enhancer.create() - 生成代理类对象 -
net.sf.cglib.proxy.MethodInterceptor
API 参数 返回值/说明 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) 第一个参数obj为代理类对象;第二个参数为目标类对应中对应的方法;第三个参数为目标类对象中对应的方法执行时传递的参数;第四个参数是代理类对象中的对应方法 返回值一般便将代理类对象对应方法的执行结果返回
使用Cglib需要导包
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
创建一个目标类,在这里为了体现Cglib的效果,目标类没有实现任何接口
public class UserServiceImpl {public String getName(){System.out.println("目标方法执行");return "zhangsan";}
}
编写测试代码
public class ProxyTest {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();UserServiceImpl userServiceProxy = Enhancer.create(UserServiceImpl.class,new InvocationHandler() {//代理类如何代理@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("目标方法执行之前");Object invoke = method.invoke(userService, args);System.out.println("目标方法执行之后");return invoke;}});String name = userServiceProxy.getName();System.out.println(name);}
}
3.3 行为型模式
3.3.1 责任链(Responsibility Chain)
责任链是一种行为设计模式,允许请求沿着链进行发送。收到请求后,每个处理者均可对请求进行处理或者将其传递给链上的下一个处理者。
对应的类关系如下
我们先举一个简单的例子,提供三个处理器,并处理好其先后关系,然后分别依次处理,那么我们要做的事情拆解如下
- 定义三个不同的处理器
- 这三个处理器做的是类似的事情,那么可以抽象一个接口或抽象类,接下来就是关于抽象类中的方法
- 要处理先后关系,可以提供一个方法来处理处理器的顺序关系
- 要做处理器的核心方法完成业务的处理,每个处理器的处理方法的业务不同
将一些共性的部分放置在一个基类中,其中提供的成员变量next能够维护顺序关系,通过调用其提供的setNext方法完成顺序关系的维护,handle方法能够提供不同的
public abstract class AbstractHandler {AbstractHandler next;public void setNext(AbstractHandler next){System.out.println("已经设置" + this.getClass().getSimpleName() + "的下一级为" + next.getClass().getSimpleName());this.next = next;}public abstract void handle();
}
三个处理器
public class Level1Handler extends AbstractHandler{@Overridepublic void handle() {System.out.println("一级处理器正在处理");if (next != null) {next.handle();}}
}
public class Level2Handler extends AbstractHandler{@Overridepublic void handle() {System.out.println("二级处理器正在处理");if (next != null) {next.handle();}}
}
public class Level3Handler extends AbstractHandler{@Overridepublic void handle() {System.out.println("三级处理器正在处理");if (next != null) {next.handle();}}
}
最终的测试
public class ChainExecution {public static void main(String[] args) {Level1Handler level1Handler = new Level1Handler();Level2Handler level2Handler = new Level2Handler();Level3Handler level3Handler = new Level3Handler();level2Handler.setNext(level3Handler);level1Handler.setNext(level2Handler);level1Handler.handle();}
}
next = next;
}
public abstract void handle();
}
三个处理器```java
public class Level1Handler extends AbstractHandler{@Overridepublic void handle() {System.out.println("一级处理器正在处理");if (next != null) {next.handle();}}
}
public class Level2Handler extends AbstractHandler{@Overridepublic void handle() {System.out.println("二级处理器正在处理");if (next != null) {next.handle();}}
}
public class Level3Handler extends AbstractHandler{@Overridepublic void handle() {System.out.println("三级处理器正在处理");if (next != null) {next.handle();}}
}
最终的测试
public class ChainExecution {public static void main(String[] args) {Level1Handler level1Handler = new Level1Handler();Level2Handler level2Handler = new Level2Handler();Level3Handler level3Handler = new Level3Handler();level2Handler.setNext(level3Handler);level1Handler.setNext(level2Handler);level1Handler.handle();}
}
责任链模式降低了系统之间的耦合性,提升了系统的可扩展性。在很多中间件、框架的内部大量地使用了该种设计模式,比如Filter的执行过程等。
4 附录 思维导图
相关文章:

DesignPattern设计模式
1 前言 1.1 内容概要 理解使用设计模式的目的掌握软件设计的SOLID原则理解单例的思想,并且能够设计一个单例理解工厂设计模式的思想,能够设计简单工厂,理解工厂方法了解建造者模式的思想,掌握其代码风格理解代理设计模式的思想&a…...

3.ChatGPT在教育领域的应用:教学辅助与案例分享(3/10)
ChatGPT在教育领域的应用:教学辅助与案例分享 引言 在21世纪的教育领域,技术革新正以前所未有的速度改变着传统的教学和学习方式。随着人工智能(AI)的快速发展,教育技术(EdTech)领域迎来了新的…...

Kafka+PostgreSql,构建一个总线服务
之前开发的系统,用到了RabbitMQ和SQL Server作为总线服务的传输层和存储层,最近一直在看Kafka和PostgreSql相关的知识,想着是不是可以把服务总线的技术栈切换到这个上面。今天花了点时间试了试,过程还是比较顺利的,后续…...

电脑怎么录屏?四款录屏工具分享
作为一个刚刚踏入视频创作领域的新手,我一直在寻找一款适合自己的录屏软件。最近,我尝试了四款市面上比较热门的录屏工具。今天,就让我来分享一下我的使用体验,希望能给同样在寻找录屏软件的朋友们一些参考。 一、福昕录屏大师 …...

Java代码审计篇 | ofcms系统审计思路讲解 - 篇4 | XXE漏洞审计
文章目录 Java代码审计篇 | ofcms系统审计思路讲解 - 篇4 | XXE漏洞审计0. 前言1. XXE代码审计【有1处】1.1. 搜索JRXmlLoader1.1.1. JRAntApiWriteTask1.1.2. JRAntUpdateTask1.1.3. TableReportContextXmlRule1.1.4. JasperCompileManager【存在漏洞】 1.2. 搜索XMLReader1.2…...

Leetcode 每日一题:Word Ladder
写在前面: 今天我们来看一道图论的题,这道题目是我做过目前最难与图论联想到的一道题目之一。如果没有提示的话,我们很容易往 dp 等解决 array 问题的方向去解决它,经过我超过 2个小时的思考我觉得这种方向是没前途的~…...

c++ 编辑器 和 编译器 的详细解释
在 C 开发中,编辑器 和 编译器 是两个不同的工具,分别在编写代码和生成可执行文件的过程中起着不同的作用。下面是它们的详细介绍: 1. 编辑器(Editor) 编辑器 是用来编写和编辑代码的工具。C 代码就是通过编辑器编写…...

计算机视觉(二)—— MDPI特刊推荐
特刊征稿 01 期刊名称: Applied Computer Vision and Pattern Recognition: 2nd Volume 截止时间: 摘要提交截止日期:2024年10月30日 投稿截止日期:2024年12月30日 目标及范围: 包括但不限于以下领域:…...

交叉编译工具链的安装及带wiringPi库的交叉编译实现
交叉编译工具链的安装及带wiringPi库的交叉编译实现 交叉编译的概念交叉编译工具链的安装下载交叉编译工具链配置环境遍变量编译程序到ARM平台 带wiringPi库的交叉编译下载编译wiringPi库调用树莓派的wringPi库 交叉编译的概念 交叉编译是在一个平台上生成另一个平台上的可执行…...

java: 程序包org.junit.jupiter.api不存在
明明idea没有报错,引用包也没问题,为啥提示java: 程序包org.junit.jupiter.api不存在? 配置!还TMD是配置! 如果是引用包的版本不对或者其他,直接就是引用报错或者pom里面飘红了。 这个应该是把generat…...

代码随想录刷题day32丨动态规划理论基础,509. 斐波那契数, 70. 爬楼梯, 746. 使用最小花费爬楼梯
代码随想录刷题day32丨动态规划理论基础,509. 斐波那契数, 70. 爬楼梯, 746. 使用最小花费爬楼梯 1.动态规划理论基础 动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题…...
为什么矩阵特征值之和等于主对角线元素之和,特征值乘积等于行列式值
首先给出特征值和特征向量的定义。 设A是n阶矩阵,如果数λ和n维非零向量x使关系式 Axλx (1) 成…...

学生学籍管理系统可行性分析报告
引言 一、编写目的 随着科学技术的不断提高,计算机科学日渐成熟,其强大的功能已为人们深刻认识,它已进入人类社会的各个领域并发挥着越来越重要的作用。而学籍管理系统软件,可广泛应用于全日制大、中小学及其他各类学校,系统涵盖了小学、初中、高中学籍…...

C#排序算法新境界:深度剖析与高效实现基数排序
基数排序(Radix Sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数进行比较。具体来说,基数排序有两种方法: 最低位优先(LSD, Least Significant Digit f…...

玩机搞机-----如何简单的使用ADB指令来卸载和冻结系统应用 无需root权限 详细操作图示教程
同类博文: 玩机搞机---卸载内置软件 无root权限卸载不需要的软件 安全卸载_无需root卸载彻底内置软件-CSDN博客 在很多时候我们需要卸载一些系统级的app。但如果直接手机端进行卸载的话。是无法正常卸载的。其实我们可以通过有些成品工具或者完全靠ADB指令来进行卸…...

如何通过 Apache Camel 将数据导入 Elasticsearch
作者:来自 Elastic Andre Luiz 使用 Apache Camel 将数据提取到 Elasticsearch 的过程将搜索引擎的稳健性与集成框架的灵活性相结合。在本文中,我们将探讨 Apache Camel 如何简化和优化将数据提取到 Elasticsearch。为了说明此功能,我们将实…...

打造民国风格炫酷个人网页:用HTML和CSS3传递民国风韵
附源码!!! 感谢支持 小弟不断创作网站demo感兴趣的可以关注支持一下 对了 俺在结尾带上了自己用的 背景 大家可以尝试换一下效果更好哦~~~ 如何创建一个民国风格的炫酷网页 在这篇博客中,我们将展示如何制作一个结合民国风格和…...

豆包MarsCode编程助手:产品功能解析与应用场景探索!
随着现代技术的不断进化升级,人工智能正在逐步改变着我们的日常工作方式。特别是对于复杂的项目,代码编写、优化、调试、测试等环节充满挑战。为了简化这些环节、提高开发效率,许多智能编程工具应运而生,豆包MarsCode 编程助手就是…...

爬虫全网抓取
爬虫全网抓取是指利用网络爬虫技术,通过自动化的方式遍历互联网上各个网站、论坛、博客等,从这些网页中提取所需的数据。它通常涉及以下几个步骤: 目标设定:确定要抓取哪些类型的网页内容,比如新闻、商品信息、用户评论…...

【计算机组成原理】详细解读带符号整数在计算机中的运算
有符号整数的运算 导读一、补码的优势二、补码的加法运算三、补码的减法运算四、原码、反码、补码的特性结语 导读 大家好,很高兴又和大家见面啦!!! 经过前面的介绍,我们已经初步认识了有符号整数的三种表示形式&…...

vue3常见的bug 修复bug
Vue 3 作为 Vue.js 的最新版本,在性能、开发体验以及代码可维护性等方面带来了显著的提升。然而,就像任何软件框架一样,Vue 3 在使用过程中也可能遇到一些典型的bug或问题。以下是一些可能遇到的典型问题: 响应式系统相关的问题&…...

C++课程笔记 类和对象
类概念 结构体:只要属性 类:有属性也有方法 c可以省略struct c不行 #include<iostream> using namespace std;typedef struct queue1 {int a;queue1 q() {queue1 q(2);return q;};queue1(){}queue1(int qa){a qa;} }q1; int main() {queue1 Q1;…...

提问即创作:用Prompt提示词引领AI灵感爆发
文章目录 🍊AI内容创作的精髓:提示词Prompt1 什么是提示词工程?1.1 提示词是如何影响AI的输出结果?1.2 提示词的原理是什么1.3 提示词工程师的前景1.4 谁能成为提示词工程师?1.5 提示词的未来前景 2 提示词的基本书写技巧3 常见的提示词框架…...

一码空传临时网盘PHP源码,支持提取码功能
源码介绍 一码空传临时网盘源码V2.0免费授权,该源码提供了一个简单易用的无数据库版临时网盘解决方案。前端采用了layui开发框架,后端使用原生PHP编写,没有引入任何开发框架,保持了代码的简洁和高效。 这个程序使用了一个无数据…...

自然语言处理实战项目
自然语言处理实战项目 自然语言处理(NLP, Natural Language Processing)是人工智能的重要分支之一,致力于让计算机理解、生成并与人类进行语言交互。随着深度学习、神经网络和大数据的发展,NLP技术在近年来取得了飞跃性的进展&am…...

人工智能物联网的去中心化和分布式学习:全面综述、新兴挑战和机遇
这篇论文的标题是《Decentralized and Distributed Learning for AIoT: A Comprehensive Review, Emerging Challenges, and Opportunities》,作者是Hanyue Xu, Kah Phooi Seng, Li Minn Ang, 和 Jeremy Smith。论文发表在IEEE Access期刊上,接收日期为2…...

滑动窗口算法—最小覆盖子串
题目 ”最小覆盖子串“问题,难度为Hard,题目如下: 给你两个字符串 S 和 T,请你在 S 中找到包含 T 中全部字母的最短子串。如果 S 中没有这样一个子串,则算法返回空串,如果存在这样一个子串,则可…...

应用案例|开源 PolarDB-X 在互联网安全场景的应用实践
背景介绍 中盾数科集团始创于2012年,是由网络安全服务而发展起来的科技型、多元化的企业集团。旗下包括网络安全服务、信创一体化服务、箱式液冷、区块链、位置服务、视觉服务等六大板块,业务覆盖湖南、甘肃、贵州等多个省份。 业务挑战 中盾集团基于A…...

【大数据】MapReduce的“内存增强版”——Spark
【大数据】MapReduce的“内存增强版”——Spark 文章脉络 Spark架构 Spark-core SparkConf 和 SparkContext RDD Spark集群 Spark-sql 在大数据时代,数据处理和分析成为企业竞争的重要手段。Hadoop作为大数据处理的基石,其核心组件MapReduce在众多…...

o1模型:引领AI技术在STEM领域的突破与应用
o1模型是OpenAI最新推出的大型语言模型,它在多个领域展现出了卓越的能力,被认为是AI技术发展的一个重要里程碑。以下是对o1模型的详细介绍和分析: o1模型的简介和性能评估 o1模型在物理、化学、生物学等领域的基准任务上达到了博士生水平&…...