代理模式(结构型模式)

目录

1、概述

2、结构

2.1、角色分类

2.2、类图

3、静态代理

3.1、案例类图

3.2、案例代码

4、JDK 动态代理

4.1、案例代码

4.2、底层原理

4.3、执行流程说明

5、CGLib 动态代理

5.1、案例代码

6、三种代理的对比

6.1、JDK代理和CGLib代理

6.2、动态代理和静态代理

7、优缺点

7.1、优点

7.2、缺点

8、使用场景

8.1、远程(Remote)代理

8.2、防火墙(Firewall)代理

8.3、保护(Project or Access)代理

1、概述

由于某些原因需要给某对象提供一个代理以控制该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的媒介。

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。

Java 中的代理按照生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则在 Java 运行时动态生成,程序结束时内存会自动释放。动态代理又分为 JDK 动态代理和 CGLib 动态代理。

2、结构

2.1、角色分类

代理(Proxy)模式分为三种角色,分别如下:

(1)抽象主题(Subject)角色:通过接口或抽象类声明真实主题和代理对象实现的业务方法。

(2)具体主题(Real Subject)角色:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,也叫做被委托角色、被代理角色。

(3)代理(Proxy)角色:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

2.2、类图

3、静态代理

静态代理是由程序创建或者特定工具自动生成源代码,在程序运行前,代理类的.class文件已经存在。

案例:

房东出租房屋,将出租和收回房子的事情都交给中介来完成,中介出租房屋后可以赚取到中介费。

3.1、案例类图

3.2、案例代码
/**
  * @ClassName IHouseOwner
  * @Description 出租房屋的接口-抽象主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public interface IHouseOwner {
 ​
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     void rentHouse();
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     void takeBackHouse();
 }
 ​
 ​
 /**
  * @ClassName HouseOwner
  * @Description 房东-具体主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwner implements IHouseOwner {
 ​
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     @Override
     public void rentHouse() {
         System.out.println("房屋闲置,房东要出租房屋");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     @Override
     public void takeBackHouse() {
         System.out.println("房屋到期,房东要收回房屋");
     }
 }
 ​
 ​
 /**
  * @ClassName HouseOwnerProxy
  * @Description 代理角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwnerProxy implements IHouseOwner {
 ​
     private IHouseOwner houseOwner;
 ​
     public HouseOwnerProxy(IHouseOwner houseOwner) {
         this.houseOwner = houseOwner;
     }
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     @Override
     public void rentHouse() {
         this.houseOwner.rentHouse();
         System.out.println("帮房东出租了房子,挣取佣金2000元");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     @Override
     public void takeBackHouse() {
         this.houseOwner.takeBackHouse();
         System.out.println("帮房东收回了房子");
     }
 }
 ​
 ​
 /**
  * @ClassName Client
  * @Description 业务场景
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class Client {
     public static void main(String[] args) {
         //创建抽象主题角色
         IHouseOwner houseOwner = new HouseOwner();
         //创建代理类,通过代理类来完成具体主题角色需要做的事情
         HouseOwnerProxy proxy = new HouseOwnerProxy(houseOwner);
         proxy.rentHouse();
         proxy.takeBackHouse();
     }
 }

执行结果:

房屋闲置,房东要出租房屋

帮房东出租了房子,挣取佣金2000元

房屋到期,房东要收回房屋

帮房东收回了房子

从上面代码中可以看出业务场景直接访问的是 IHouseOwner 类对象,也就是说 IHouseOwner 作为访问对象和目标对象的中介,同时也对 rentHouse() 和 takeBackHouse() 方法做出了增强。

4、JDK 动态代理

JDK 动态代理要求目标对象实现一个接口。

Java 中提供了一个动态代理类 Proxy,Proxy 不是静态代理中所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance())来获取代理对象。

4.1、案例代码

在JDK动态代理中,不再需要去手动创建一个代理类来完成目标对象的执行方法。

/**
  * @ClassName IHouseOwner
  * @Description 抽象主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public interface IHouseOwner {
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     void rentHouse();
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     void takeBackHouse();
 }
 ​
 /**
  * @ClassName HouseOwner
  * @Description 房东-具体主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwner implements IHouseOwner {
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     @Override
     public void rentHouse() {
         System.out.println("房屋闲置,房东要出租房屋");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     @Override
     public void takeBackHouse() {
         System.out.println("房屋到期,房东要收回房屋");
     }
 }
 ​
 /**
  * @ClassName ProxyFactory
  * @Description  获取代理对象的工厂类
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class ProxyFactory {
     //1、声明目标对象
     private HouseOwner houseOwner = new HouseOwner();
 ​
     /**
      * @Description: 获取代理对象的方法
      * @Author: chengjunyu
      */
     public IHouseOwner getHouseOwner() {
         //2、返回代理对象
         /*
          * ClassLoader loader: 类加载器,用于加载代理类(程序运行中动态的在内存中生成的类),通过目标对象获取
          * Class<?>[] interfaces:代理类实现的接口的字节码对象
          * InvocationHandler h:代理对象的调用处理程序
          */
         IHouseOwner proxyObject = (IHouseOwner) Proxy.newProxyInstance(
                 houseOwner.getClass().getClassLoader(),
                 houseOwner.getClass().getInterfaces(),
                 //匿名内部类,重写 invoke 方法
                 new InvocationHandler() {
                     /*
                      * Object proxy:代理对象,和 proxyObject 是同一个对象,在 invoke 方法中基本上不使用
                      * Method method:对接口中的方法进行封装的 method 对象
                      * Objects[] args:调用方法的实际参数
                      */
                     @Override
                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                         Object object =  method.invoke(houseOwner, args);
                         if ("rentHouse".equals(method.getName()))  {
                             System.out.println("帮房东出租了房子,挣取佣金2000元");
                         }
                         if ("takeBackHouse".equals(method.getName())) {
                             System.out.println("帮房东收回了房子");
                         }
                         return object;
                     }
                 }
         );
         return proxyObject;
     }
 }
 ​
 /**
  * @ClassName Client
  * @Description 业务场景
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class Client {
     public static void main(String[] args) {
         //获取代理对象
         //1、创建代理对象工厂
         ProxyFactory proxyFactory = new ProxyFactory();
         //2、使用factory对象的方法获取代理对象
         IHouseOwner houseOwner = proxyFactory.getHouseOwner();
         //调用租房方法
         houseOwner.rentHouse();
         //调用收回房屋方法
         houseOwner.takeBackHouse();
     }
 }

执行结果:

房屋闲置,房东要出租房屋

帮房东出租了房子,挣取佣金2000元

房屋到期,房东要收回房屋

帮房东收回了房子

4.2、底层原理

在学习JDK动态代理的底层原理之前,可以先考虑一个问题:ProxyFactory是代理类吗?

ProxyFactory 不是代理模式中所说的代理类,代理类是程序在运行过程中动态的在内存中生成的类。

我么可以通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构,操作步骤如下:

1、运行指令 java -jar arhtas-boot.jar 启动 Arthas;

2、运行指令 jad com.sun.proxy.$Proxy0 获取该类源码;

3、优化源码,优化后的源码如下:

 
package com.sun.proxy;
 ​
 import com.design.pattern.proxy.jdkProxy.IHouseOwner;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.lang.reflect.UndeclaredThrowableException;
 ​
 public final class $Proxy0 extends Proxy implements IHouseOwner {
     private static Method m1;
     private static Method m2;
 ​
     //构造方法提供了有参构造,参数为 InvocationHandler 对象,赋值给了父类 Proxy
     public $Proxy0(InvocationHandler invocationHandler) {
         super(invocationHandler);
     }
 ​
     static {
         //获取IHouseOwner字节码对象后获取方法
         m1 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("rentHouse", new Class[0]);
         m2 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("takeBackHouse", new Class[0]);
     }
 ​
     //调用了 InvocationHandler 的子实现类对象的 invoke 方法
     public final void rentHouse() {
         //此处h即为InvocationHandler对象,this表示本类,m1为rentHouse方法
         this.h.invoke(this, m1, null);
     }
 ​
     public final void takeBackHouse() {
         this.h.invoke(this, m2, null);
     }
 }
 ​
 //父类Proxy
 public class Proxy {
     protected InvocationHandler invocationHandler;
 }

从上面的类中,我们可以看到以下几个信息:

1、代理类($Proxy0)实现了 IHouseOwner ,这也就印证了我们之前说的真实类和代理类实现同样的接口;

2、代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

4.3、执行流程说明

根据JDK动态代理示例代码和底层原理代码,我们可以分析出JDK动态代理的执行流程如下:

1、在业务场景中通过代理对象调用 rentHouse() 和 takeBackHouse();

2、根据多态的特性,执行的是代理类($Proxy0)中的 rentHouse() 和 takeBackHouse();

3、代理类($Proxy0)中的 rentHouse() 方法和 takeBackHouse() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke() 方法;

4、invoke 方法通过反射执行了真实对象所属类(HouseOwner)中的rentHouse() 和 takeBackHouse()。

5、CGLib 动态代理

有时候目标对象只是一个单独的对象,并没有实现接口,这个时候就可以使用CGLIB代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

CGLIB动态代理基于继承来实现代理,所以无法对 final 类、private 和 static 方法实现代理。

5.1、案例代码

同样是上面的案例,这里我们使用CGLIB代理实现。

如果没有定义IHouseOwner接口,只定义了HouseOwner(房屋所有者类),很显然JDK动态代理就无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

CGLIB是第三方提供的包,所以在Spring框架下需要引入jar包的maven坐标。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->

<dependency>

<groupId>cglib</groupId>

<artifactId>cglib</artifactId>

<version>3.3.0</version>

</dependency>

代码如下:

 
/**
  * @ClassName HouseOwner
  * @Description 房东-具体主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwner {
 ​
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     public void rentHouse() {
         System.out.println("房屋闲置,房东要出租房屋");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     public void takeBackHouse() {
         System.out.println("房屋到期,房东要收回房屋");
     }
 }
 ​
 ​
 /**
  * @ClassName ProxyFactory
  * @Description 代理工厂类
  * @Author chengjunyu
  * @Date 2022/12/10
  * @Version V1.0
  */
 public class ProxyFactory implements MethodInterceptor {
 ​
     private HouseOwner houseOwner = new HouseOwner();
 ​
     public HouseOwner getProxyObject() {
         //1、创建Enhancer对象,类似于JDK动态代理中的Proxy类
         Enhancer enhancer = new Enhancer();
         //2、设置父类的字节码对象,指定父类
         enhancer.setSuperclass(HouseOwner.class);
         //3、设置回调函数
         enhancer.setCallback(this);
         //4、创建代理对象,即目标类对象的子对象
         HouseOwner proxyObject = (HouseOwner) enhancer.create();
         return proxyObject;
     }
 ​
     /**
      * @Description: 方法所属类的回调函数
      * @Author: chengjunyu
      */
     @Override
     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
         System.out.println("调用了回调函数");
         Object object = method.invoke(this.houseOwner, objects);
         return object;
     }
 }
 ​
 ​
 /**
  * @ClassName Client
  * @Description 业务场景类
  * @Author chengjunyu
  * @Date 2022/12/10
  * @Version V1.0
  */
 public class Client {
     public static void main(String[] args) {
         //创建代理工厂对象
         ProxyFactory factory = new ProxyFactory();
         //获取代理对象
         HouseOwner proxyObject = factory.getProxyObject();
         //调用代理对象中的方法
         proxyObject.rentHouse();
         proxyObject.takeBackHouse();
     }
 }

6、三种代理的对比

6.1、JDK代理和CGLib代理

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行处理,因为CGLib原理是动态生成被代理的子类。

在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib的代理效率低一些,但是到JDK1.8的时候,JDK代理效率高于CGLib代理,所以如果有接口使用JDK动态代理,如果没有接口则使用CGLib代理。

6.2、动态代理和静态代理

动态代理和静态代理相比较,最大的好处就是接口中声明的所有方法都被转移到调用处理器一个集中的方法处理(InvocationHandler.invoke)。这样,在接口中方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

如果接口增加了一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现这个方法,增加了代理维护的复杂度,而动态代理不会出现这样的问题。

7、优缺点

7.1、优点

(1)代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

(2)代理对象可以扩展目标对象的功能;

(3)代理模式能够将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

7.2、缺点

(1)增加了系统的复杂度。

8、使用场景

8.1、远程(Remote)代理

本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常,为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个端口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

8.2、防火墙(Firewall)代理

当你将浏览器配置称为使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

8.3、保护(Project or Access)代理

控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的访问权限。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型(Large Models):探索人工智能领域的新边界

&#x1f31f;文章目录 &#x1f31f;大模型的定义与特点&#x1f31f;模型架构&#x1f31f;大模型的训练策略&#x1f31f;大模型的优化方法&#x1f31f;大模型的应用案例 随着人工智能技术的飞速发展&#xff0c;大模型&#xff08;Large Models&#xff09;成为了引领深度…

使用ROCm的HIP API向量加法程序

一、向量加法程序 Radeon Open Compute (ROCm) 是一个开源平台&#xff0c;用于加速高性能计算 (HPC) 和机器学习应用程序。它支持包括GPUs在内的多种硬件&#xff0c;并提供HIP (Heterogeneous-compute Interface for Portability) 作为CUDA代码的便捷转换工具。为了提供一个…

广佛站点导航助手小程序产品使用说明书

一、产品简介 广佛站点导航助手小程序是一款专为广佛地区用户设计的地铁导航工具。通过获取用户的实时位置信息&#xff0c;小程序能够迅速定位并展示离用户最近的三个地铁站点。用户可以通过本小程序轻松查找地铁站点&#xff0c;规划出行路线&#xff0c;提高出行效率。 二、…

SDK-0.7.8-Release-实体管理 - ApiHug-Release

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace 更多精彩…

Linux:环境基础开发工具使用

文章目录 前言1.Linux下的软件安装1.1 什么是软件包1.2 如何安装软件1.3 如何卸载软件 2.vim2.1 vim的基本概念2.2 vim的基本操作2.3 vim正常模式命令集2.4 vim末行模式命令集2.5 vim的操作总结 3.Linux下的编译器&#xff1a;gcc3.1 gcc的使用3.2 gcc是如何工作的3.2.1 预处理…

Micromamba 安装与使用

文章目录 一、安装 MicromambaWindows安装使用 Micromamba 一、安装 Micromamba Micromamba 是一个静态链接的 C 可执行文件&#xff0c;在 Windows 上就是一个 micromamba.exe 文件&#xff0c;下载下来就直接可以用&#xff0c;甚至都不需要专门安装。唯一需要做的就是设置 …

Python编程的十大好用工具!

Python一直以来都是程序员们的首选编程语言之一&#xff0c;其灵活性和功能强大的库使其成为解决各种问题的理想选择。在本文中&#xff0c;我们将介绍Python编程的终极十大工具&#xff0c;这些工具可以帮助您在各种领域取得成功&#xff0c;无论您是初学者还是经验丰富的开发…

python数据分析学什么东西

前言 大家好&#xff0c;小编为大家解答python数据分析重点要学什么的问题。很多人还不知道python数据分析需要什么基础&#xff0c;现在让我们一起来看看吧&#xff01; 数据分析人人都有必要掌握一点&#xff0c;哪怕只是思维也行。下面探讨Python数据分析需要学习的知识范…

JavaScript-2.对话框、函数、数组、Date、DOM

对话框 window对象封装了三个对话框用于与用户交互 提示框&#xff1a;alert(title);确认框&#xff1a;confirm(title);输入框&#xff1a;prompt(title); 确认框 包含两个按钮“确认”/“取消”&#xff0c;点击确定时&#xff0c;返回值为true // 确认框 var bool con…

微服务项目——谷粒商城

文章目录 一、项目简介&#xff08;一&#xff09;完整的微服务架构详细解释——微服务划分图&#xff08;二&#xff09;电商模式1.B2B 模式2.B2C 模式3.C2B 模式4.C2C 模式5.o2o 模式2.谷粒商城 &#xff08;三&#xff09;项目技术&特色&#xff08;四&#xff09;项目前…

Transformer 结构浅析

Transformer 结构浅析 文章目录 Transformer 结构浅析Transformer 网络结构编码器位置编码多头注意力层Add&NormFeed Forward 解码器带掩码的多头注意力层多头注意力层 预测 Transformer 网络结构 Transformer模型的网络结构如图&#xff0c;且transformer结构主要分为两部…

Linux使用docker安装RocketMQ并登录管理界面

Linux使用docker安装RocketMQ并登录管理界面 1、创建 /opt/rocketmq/docker-compose.yml和/opt/rocketmq/broker.conf两个配置文件 2、docker-compose.yml&#xff0c;并配置管理页面端口为8090 version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_…

“栈”顶到底是高地址还是低地址?

栈的增长方向永远是从杯底到杯顶&#xff0c;所以对于栈来说上面是栈底下面是栈顶&#xff0c;而对于堆来说&#xff0c;上面是堆顶下面是堆底。栈是连续分配内存的&#xff0c;如果给一个数组或对象分配内存&#xff0c;栈会选择还没分配的最小的内存地址给数组&#xff0c;在…

20240327-1-评测指标面试题

评测指标面试题 metric主要用来评测机器学习模型的好坏程度,不同的任务应该选择不同的评价指标,分类,回归和排序问题应该选择不同的评价函数. 不同的问题应该不同对待,即使都是分类问题也不应该唯评价函数论,不同问题不同分析. 回归(Regression) 平均绝对误差(MAE) 平均绝对…

Android 车载应用开发概述

前言 介绍 Android 车载应用开发 文章目录 前言一、Android Automotive OS 概述二、Android Automotive OS 架构三、常见的车载应用1、系统应用1&#xff09;SystemUI是什么开发工作 2&#xff09;Launcher是什么开发工作 3&#xff09;Settings是什么开发工作 4&#xff09;多…

使用UDP实现TCP的功能,会带来什么好处?

比较孤陋寡闻&#xff0c;只知道QUIC TCPQUIC握手延迟TCP需要三次握手TLS握手三次握手TLS握手放在一起&#xff0c;实现0RTT头阻塞问题TCP丢失保文&#xff0c;会影响所有的应用数据包基于UDP封装传输层Stream&#xff0c;Stream内部保序&#xff0c;Stream之间不存在相互影响…

实时智能应答3D数字人搭建2

先看效果&#xff1a; 3d数字人讲黑洞 根据艾媒咨询数据&#xff0c;2021年&#xff0c;中国虚拟人核心产业规模达到62.2亿元&#xff0c;带动市场规模达到1074.9亿元&#xff1b;2025年&#xff0c;这一数据预计将达到480.6亿元与6402.7亿元&#xff0c;同比增长迅猛。数字人可…

C语言:指针详解(1)

目录 一、内存和地址 1.内存 2.究竟该如何理解编址 二、指针变量和地址 1.取地址操作符(&) 2.解引用操作符(*) 3.指针变量的大小 三、指针变量类型的意义 1.指针的解引用 2.指针-整数 3.void*指针 四、const修饰指针 1.const修饰变量 2.const修饰指针变量 五…

C语言 | Leetcode C语言题解之第19题删除链表的倒数第N个结点

题目&#xff1a; 题解&#xff1a; struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {struct ListNode* dummy malloc(sizeof(struct ListNode));dummy->val 0, dummy->next head;struct ListNode* first head;struct ListNode* second dummy;f…

CSS核心样式-02-盒模型属性及扩展应用

目录 三、盒模型属性 常见盒模型区域 盒模型图 盒模型五大属性 1. 宽度 width 2. 高度 height 3. 内边距 padding 四值法 三值法 二值法 单值法 案例 4. 边框 border 按照属性值的类型划分为三个单一属性 ①线宽 border-width ②线型 border-style ③边框颜色 bo…