DesignPattern设计模式

news2024/9/24 0:53:19

1 前言

1.1 内容概要

  1. 理解使用设计模式的目的
  2. 掌握软件设计的SOLID原则
  3. 理解单例的思想,并且能够设计一个单例
  4. 理解工厂设计模式的思想,能够设计简单工厂,理解工厂方法
  5. 了解建造者模式的思想,掌握其代码风格
  6. 理解代理设计模式的思想,理解动态代理类和委托类(目标类)之间的关系,熟悉代理对象调用方法的执行过程,熟练使用动态代理完成代码设计
  7. 了解责任链设计模式的思想

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("未知车辆") {
            @Override
            public 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("未知车辆") {
                    @Override
                    public 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{
    @Override
    public Tesla getTesla() {
        return new ModelY();
    }
}
public class Model3Factory implements TeslaFactory{
    @Override
    public 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的家居产品。不同的厂家产品线都非常丰富,涵盖TVFreezer等。设计一套代码程序,根据用户选择的厂家,提供对应的配套产品。

对应的类关系如下:

在这里插入图片描述

public abstract class AbstractFurnitureFactory {

    public abstract TV createTV();
    public abstract Freezer createFreezer();
}
public class MiFurnitureFactory extends AbstractFurnitureFactory{
    @Override
    public TV createTV() {
        return new MiTV();
    }

    @Override
    public Freezer createFreezer() {
        return new MiFreezer();
    }
}
public class HaierFurnitureFactory extends AbstractFurnitureFactory{
    @Override
    public TV createTV() {
        return new HaierTV();
    }

    @Override
    public 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类

  1. 通过Builder类提供的build方法能够获得Phone实例
  2. 同时提供一些方法,通过这些方法能够设置build方法获得的该phone实例的属性值
  3. 要保证这些方法操作的是同一个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 {
    @Override
    public void insert() {
        System.out.println("目标类执行了insert方法");
    }
}
public class UserServiceProxy implements UserService {

    UserService target;

    public UserServiceProxy(UserService target) {
        //注入委托类对象
        this.target = target;
    }

    @Override
    public void insert() {
        System.out.println("代理之前打印一个日志");
        target.insert();
        System.out.println("代理之后打印一个日志");
    }
}
@Test
    public 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() {
                    
                    //代理类如何代理
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理之前签订合约");
                        Object invoke = method.invoke(target, args);
                        System.out.println("代理完毕转账确认");
                        return invoke;
                    }
                });
    }
}
@Test
    public void test2(){
        UserService userService = new UserServiceImpl();
        //对哪个目标类进行代理,我们对UserServiceImpl进行代理
        //生成代理类对象
        UserService userServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    //代理类如何代理
                    @Override
                    public 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() {

            //代理类如何代理
            @Override
            public 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)

责任链是一种行为设计模式,允许请求沿着链进行发送。收到请求后,每个处理者均可对请求进行处理或者将其传递给链上的下一个处理者。

对应的类关系如下

在这里插入图片描述

我们先举一个简单的例子,提供三个处理器,并处理好其先后关系,然后分别依次处理,那么我们要做的事情拆解如下

  1. 定义三个不同的处理器
  2. 这三个处理器做的是类似的事情,那么可以抽象一个接口或抽象类,接下来就是关于抽象类中的方法
  3. 要处理先后关系,可以提供一个方法来处理处理器的顺序关系
  4. 要做处理器的核心方法完成业务的处理,每个处理器的处理方法的业务不同

将一些共性的部分放置在一个基类中,其中提供的成员变量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{
    @Override
    public void handle() {
        System.out.println("一级处理器正在处理");
        if (next != null) {
            next.handle();
        }
    }
}
public class Level2Handler extends AbstractHandler{
    @Override
    public void handle() {
        System.out.println("二级处理器正在处理");
        if (next != null) {
            next.handle();
        }
    }
}
public class Level3Handler extends AbstractHandler{
    @Override
    public 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{
    @Override
    public void handle() {
        System.out.println("一级处理器正在处理");
        if (next != null) {
            next.handle();
        }
    }
}
public class Level2Handler extends AbstractHandler{
    @Override
    public void handle() {
        System.out.println("二级处理器正在处理");
        if (next != null) {
            next.handle();
        }
    }
}
public class Level3Handler extends AbstractHandler{
    @Override
    public 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 附录 思维导图

请添加图片描述
请添加图片描述
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2126629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

应用层自定义协议与序列化

一、理解应用层 上一篇文章http://t.csdnimg.cn/931k6简单介绍了如何写tcp / udp 网络服务&#xff0c;但是其实始终是在应用层。 一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层。 二、再谈协议 协议是一种 "约定"。socket api 的接口, 在读…

TiDB从0到1学习笔记(精华篇)

历时四个月&#xff0c;恭喜赵老师的《TiDB从0到1》 系列文章顺利完结&#xff0c;小编再次梳理一遍文稿&#xff0c;并附注解分享给大家。 整体架构 从 TiDB 1.0 到 8.0&#xff0c;TiDB 的体系结构一直在不断演进。接下来让我们一起看看整体架构的变化。 TiDB v1 TiDB v1&…

005——栈

目录 栈 栈的定义 栈的性质 栈的应用场景 存储结构&#xff1a; Ⅰ&#xff09;采用顺序存储结构实现——顺序栈 Ⅱ&#xff09;采用链式存储结构实现——链栈-->基于单链表&#xff08;带头结点&#xff09; 栈 栈的定义 之允许在一端进行插入和删除的线性表 栈的…

安卓获取apk的公钥,用于申请app备案等

要申请app的icp备案等场景&#xff0c;需要app的 证书MD5指纹和公钥&#xff0c;示例如下&#xff1a; 步骤1&#xff1a;使用keytool从APK中提取证书 1. 打开命令行&#xff0c;cd 到你的apk目录&#xff0c;如&#xff1a;app/release 2. 解压APK文件&#xff1a; unzip yo…

一维稳态与非稳态导热的详细分析

目录 引言 一维稳态导热 应用实例&#xff1a;单层平壁导热 数值求解&#xff1a; 一维非稳态导热 应用实例&#xff1a;单层平壁的非稳态导热 温度变化阶段 表格总结&#xff1a; 引言 热传导&#xff08;Heat Conduction&#xff09;是热量在物体内部通过微观粒子的相…

批量从word切割说话人!!对于转录后的文本进行纯数据清洗切割和区分说话人-批量从word切割说话人

battle AI的全过程 文章目录 初步切割同时基于文本中提取的动词变化类别做切割以及发言人变化路径设置 迁移模型到GPUbert输出空白debugCPU能运行的语义相似度代码GPU能用了但是没有加切分规则的代码 根据动词变化切分把发言人替换老师和学生的代码读取txt的代码先区分说话人&a…

Qt常用控件——QLCDNumber

文章目录 QLCDNumber核心属性倒计时小程序倒计时小程序相关问题 QLCDNumber核心属性 QLCDNumber是专门用来显示数字的控件&#xff0c;类似于这样&#xff1a; 属性说明intValue获取的数字值(int).value获取的数字值(double)和intValue是联动的例如value设为1.5&#xff0c;in…

黑马点评22——最佳实践-批处理优化

文章目录 pipeline和mset集群模式下的批处理问题 pipeline和mset pipeline就是大数据量的导入&#xff0c;pipeline是在单机模式下的。 redis的处理耗时相比较网络传输的耗时其实是比较低的。 所以我们最好采用批处理&#xff0c; 集群模式下的批处理问题 在集群环境…

生动灵活,MegActor重磅升级!旷视科技发布MegActor-Σ:首个基于DiT的人像动画方法!

文章链接&#xff1a;https://arxiv.org/pdf/2408.14975 项目链接&#xff1a;https://megactor-ops.github.io/ 亮点直击 一种新颖的混合模态扩散Transformer&#xff08;DiT&#xff09;&#xff0c;能够有效整合音频和视觉控制信号。相较于之前基于UNet的方法&#xff0c;这…

qsort的理解--加强对指针的理解

前言&#xff1a;前面我们学习指针变量&#xff0c;数组指针变量&#xff0c;函数指针变量&#xff1b;这些实际上都是变量&#xff0c;实质上是在内存中开辟一块空间&#xff1b;而这些变量存储的都是地址。还有指针数组&#xff0c;函数指针数组&#xff0c;这指的是把多个地…

AIGC大模型扩图:Sanster/IOPaint(4)

AIGC大模型扩图&#xff1a;Sanster/IOPaint&#xff08;4&#xff09; 用大模型实现AI扩大一张图的周边区域&#xff0c;变得更大&#xff0c;当然必须契合原图&#xff0c;和原图浑然一体。 1、这次模型换用 Sanster/PowerPaint-V1-stable-diffusion-inpainting 启动&#xf…

文件对比工具--BeyondCompare

&#x1f496;简介 Beyond Compare 是一款功能强大的文件和文件夹比较工具&#xff0c;由Scooter Software开发。它可以帮助用户轻松地比较文件和文件夹的差异&#xff0c;并且可以合并变化、同步文件以及备份重要数据 &#x1f4bb;环境 windows &#x1f4d6;版本 Beyon…

RocketMQ安装与使用

什么是消息中间件 消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流&#xff0c;并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型&#xff0c;它可以在分布式环境下扩展进程间的通信。对于消息中间件&#xff0c;常见的角色大致也就有Prod…

OpenCV高阶操作

在图像处理与计算机视觉领域&#xff0c;OpenCV&#xff08;Open Source Computer Vision Library&#xff09;无疑是最为强大且广泛使用的工具之一。从基础的图像读取、 1.图片的上下&#xff0c;采样 下采样&#xff08;Downsampling&#xff09; 下采样通常用于减小图像的…

日志相关知识

1.作用 a.为了代替System.out.println()&#xff0c;可以定义格式&#xff0c;重定向文件等。 b.可以存档&#xff0c;便于追踪问题。 c.可以按级别分类&#xff0c;便于打开或关闭某些级别。 d.可以根据配置文件调整日志&#xff0c;无需修改代码。 …

如何逆转Instagram账号流量减少?实用技巧分享

Instagram作为全球十大社媒之一&#xff0c;不仅是个人分享生活的平台&#xff0c;还是跨境卖家进行宣传推广和客户开发的关键工具。在运营Instagram的过程中&#xff0c;稍有不慎就容易出现账号被限流的情况&#xff0c;对于账号状态和运营工作的进行都十分不利。 一、如何判断…

图片预览、拖拽和缩放组件分享

业务场景 项目中不需要点击小图然后展示大图&#xff0c;类似于elementui中的Image图片组件。适用于直接展示大图&#xff0c;支持拖拽和缩放的场景&#xff0c;比如&#xff1a;用户需要比对两种数据的图片展示&#xff0c;左右两侧进行展示。 效果图 使用方式 在components…

宏任务和微任务+超全面试真题

概念 微任务和宏任务是在异步编程中经常使用的概念&#xff0c;用于管理任务的执行顺序和优先级。 宏任务&#xff1a;setTimeout, setInterval&#xff0c;I/O 操作和 UI 渲染等。微任务&#xff1a; Promise 回调、async/await等 微任务通常比宏任务具有更高的优先级。 执…

S7-1500替代S7-300全解析系列

硬件篇上 01 概述工控人加入PLC工业自动化精英社群 2022年十月初的时候&#xff0c;想必工控圈的小伙伴们都被S7-300系列即将于2023年10月1日退市的消息刷屏了吧&#xff1f;倒退到2020年的10月1日&#xff0c;同样伴随我们多年的ET200S系列也已经悄无声息地退市了。在感叹经…

GEE 将本地 GeoJSON 文件上传到谷歌资产

在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;Google Earth Engine&#xff08;GEE&#xff09;是一个强大的平台&#xff0c;它允许用户处理和分析大规模地理空间数据。本文将介绍如何使用 Python 脚本批量上传本地 GeoJSON 文件到 GEE 资产存储&#xff0c;这对…