当前位置:首页|资讯|人工智能|Notion AI|文心一言|编程

写这篇设计模式的文章 我用了两个人工智能工具

作者:极客架构师发布时间:2023-05-11

与其说这篇文章是讲解代理模式的,不如说它是对我正在使用的人工智能工具notion AI的功能展现。我的这篇文章,里面所有的Java代码和UML类图,都是采用notion AI生成的,原本至少一周的工作量,不到三天就写完了。里面的插图,是用文心一言生成的,虽然很丑,但是我不用再担心图片侵权了。





极客架构师——专注架构师成长。

大家好,我是码农老吴。

本期是《架构师基本功之设计模式》第20期。

自从第19期,分享了组合模式之后,后面分享的几个设计模式(适配器模式,桥接模式,装饰器模式)都没有写长文,主要原因是我这一段时间,正在探索新的知识分享形式,研究如何编写剧本,写完剧本直接录制视频了,文章一直没顾上写。代理模式是我们分享的最后一个设计模式,感觉不写长文,不足以表达对它的重视。

分享思路

  • 案例-订单服务模块

  • 第一版代码:无设计模式

  • 案例升级-打印方法的参数信息

  • 第二版代码:基于静态代理模式

  • 为什么是静态的代理模式

  • 代理模式概念解析

  • 什么是动态代理模式

  • 静态代理和动态代理的比较

  • JDK中的动态代理

  • 第三版代码:基于JDK的动态代理

  • CGLIB的动态代理

  • REIS分析模型解析代理模式

  • spring aop模块

  • 第四版代码:基于spring aop的动态代理

  • 下期预告

案例-订单服务模块

为了演示代理模式,我们先编写一个基础代码,也就是第一版代码,无设计模式版本。订单服务接口提供了两个方法,用于创建订单和取消订单。

第一版代码:无设计模式

大家注意下面代码里面的注释,为了强调这些代码都是notion自动生成的,我专门让notion在注释中添加了一句“该代码是由 Notion 自动生成的”。

本篇文章所有代码,都是notion生成的,不过都是在我和notion的反复沟通中完成的。


测试代码:


案例升级-打印方法的参数信息

现在我们升级一下需求,为了测试方便,我们需要在用户调用订单服务的方法时,将用户输入的参数写入日志。这个功能我们就可以通过代理模式来解决,下面看第二版代码。

第二版代码:基于静态代理模式

UML类图

notion不能直接生成UML图,需要先生成plantUML代码,然后我再把它转为图片。

图片上面的红色文字,是我后加的,不是notion生成的。


OrderServiceProxy是我们新增的类,在代理模式中,它属于代理角色,具有以下特点:

  • 包含一个实际的订单服务对象,也就是orderService属性(被代理角色或者叫原角色)

  • 通过构造函数注入实际的订单服务对象(不是必须的)

  • 实现了IOrderService接口(确保代理角色与被代理角色具有相同的接口)

  • 每个方法的实现中,都调用了实际的订单服务对象的相应方法

  • 在每个方法的实现中,都调用了printMethodArgs方法,用于输出方法参数及其名称

要强调的是,printMethodArgs方法,它属于代理角色自己的业务功能。

为什么是静态的代理模式

静态代理模式的定义:

指在程序运行前就已经存在代理类的字节码文件,代理类和被代理类的关系在运行前就已经确定。在代理类编译时,被代理类的接口信息已经确定,以后无论调用任何被代理类对象的方法,都是通过代理类对象来间接调用。因此,静态代理模式在运行期间不会再动态地产生新的代理类,所以被称为静态代理模式。

大家看看OrderServiceProxy,是否符合静态代理模式的要求。

我们程序员手动编写的代理模式案例,基本上都是静态的,因为要从零开始实现动态代理模式,没那么容易,需要做很多准备工作,而这些准备工作,就是JDK中的代理模块,CGLIB类库和spinrg aop帮助我们完成的。

代理模式概念解析

Provide a surrogate or placeholder for another object to control access to it. 

—— GOF《Design Patterns: Elements of Reusable Object-Oriented Software》 

为其他对象提供一种代理以控制对这个对象的访问。 

——GOF《设计模式:可复用面向对象软件的基础》

代理模式的概念看起来很简单,就一句话,说明了代理模式的目的,是控制对被代理对象的访问,而恰恰是这句话,暴露了当前GOF四位前辈在定义代理模式时的历史局限性。

对访问的控制,通常我们可以理解为,对访问者权限的验证,而代理模式在实际的业务场景中,作用远远大于这个,不然的话,也不会出现下面这么多种类型的代理模式。



(注:本图是由文心一言生成,一名房产代理商业务员的照片,看起来有点吓人,双眼无神。)

本段内容由智能助手notion生成 

代理模式的常见种类:

虚拟代理:在访问一个资源时,代理先把访问的权限拦下来,然后再去请求真正的资源。虚拟代理的一个常见应用场景是图片的延迟加载,即在页面渲染时,先显示一张占位图,等图片资源加载完毕再替换为真正的图片。例如,Java中的AWT中就使用了虚拟代理,来实现对图片的延迟加载。 

远程代理:用于管理不同地址空间的对象,使得这些对象可以在不同的地址空间中进行通信。远程代理的一个常见应用场景是分布式服务框架,例如Dubbo,它使用了远程代理技术来实现跨进程或跨机器的服务调用。另外,Java中的RMI也是一种远程代理。 

安全代理:用于控制对一个对象的访问权限。安全代理的一个常见应用场景是控制对一个文件的访问权限,即只有拥有特定权限的用户才能访问该文件。例如,Java中的安全管理器就使用了安全代理。 

智能代理:在代理中加入了一些额外的逻辑,比如对被代理对象的使用进行计数、记录日志等。智能代理的一个常见应用场景是数据库连接池,即在代理中加入连接池的逻辑,控制连接的数量和使用次数。另外,Spring AOP中的切面就是一种智能代理。

上面这些代码模式的分类,基本上都是根据应用的场景来分类的,而从技术角度,将代理模式分为静态代理和动态代理,才是我们学习的重点,静态代理前面我们已经说过了,下面我们看动态代理及两者的区别。

什么是动态代理模式

动态代理是指在程序运行时动态生成代理类的代码。与静态代理相比,动态代理更加灵活,可以代理任意类型的对象,不需要为每一个被代理的类都创建一个代理类。

静态代理和动态代理的比较

静态代理和动态代理都可以实现对目标对象的访问控制,但两者的实现方式有所不同。下表列出了静态代理和动态代理在不同方面的支持情况。

总之,动态代理相比静态代理更加灵活和可扩展,并且能够避免代码冗余和维护难度增加的问题。因此,在实际的开发中,动态代理更加常用。

JDK中的动态代理

JDK中对动态代理的支持,主要是通过Proxy和InvocationHandler来提供支持的。

java.lang.reflect.Proxy

是Java中用于生成动态代理类的类。使用它可以在运行时动态创建实现特定接口的类,是实现动态代理的关键类。

java.lang.reflect.InvocationHandler

是一个接口,定义了一个方法invoke,该方法负责处理代理类的方法调用。在实现动态代理时,需要编写一个实现了该接口的类,该类负责对代理类的方法调用进行处理。

下面我们基于它们,来升级我们的代码。

第三版代码:基于JDK的动态代理

从动态代理模式的角度,OrderServiceDynamicProxy具有以下特点:

  • 实现了JDK的InvocationHandler接口

  • 在invoke方法中,通过Java的反射机制将目标对象的方法调用转发到自己的invoke方法中,在调用目标方法之前和之后,增加了自己的业务逻辑(打印方法入参)

  • getProxy方法是获取代理对象的方法,使用Java的Proxy类创建了一个代理对象,这个代理对象实现了IOrderService接口,调用代理对象的方法时,实际上是调用了OrderServiceDynamicProxy的invoke方法

注意,代理角色也有自己的业务逻辑代码,在这里就是printMethodArgs方法。

CGLIB的动态代理

在Java的软件生态中,对于一个比较热门的,基础功能的支持,JDK往往是最早的,但是往往也是最简陋的,要体验优质的服务,还需要看业内第三方框架或者类库,而对于代理模式的支持,也有不少框架,比如spring aop,Javassist,cglib,AspectJ等等。

CGLIB是一个强大的、高性能的Code Generation Library,它可以在运行时动态生成字节码,从而实现对Java类的动态代理。

总的来说,CGLIB动态代理相比JDK动态代理,性能更高,支持更多的代理类型,但需要注意的是,由于CGLIB是通过生成目标对象的子类来实现代理,所以如果目标对象被final修饰,那么就无法生成代理类了。

在实际项目中直接使用CGLIB比较少见,我们就不提供代码了。我们先总结一下代理模式,然后看spring 的aop模块实现的代码。

REIS分析模型解析代理模式

REIS分析模型是我总结的分析设计模式的一种方法论,主要包括场景、角色、交互、效果四个要素。

场景

在什么情况下,遇到了什么问题,需要使用某个设计模式。

当出现以下情况时,可能需要使用代理模式:

  • 在访问一个对象时,需要对访问进行控制。

  • 在访问一个对象时,需要增强其功能。

角色



角色,一般为设计模式出现的类,或者对象。每种角色有自己的职责。

在代理模式中,通常包含三种角色,原角色(Original role),代理角色(Proxy role),客户方角色(client role)。 原角色(Original role)

在GOF原始的代理模式中,原角色被称为Subject角色,在我的设计模式知识体系,统一约定为原角色,在代理模式的语境中,原角色就是被代理的角色,提供原始的业务逻辑,与我前面刚刚分享的装饰器模式中的原角色,是同一个道理。

原角色里面通常包含接口和具体类,在GOF原始的代理模式中,把原角色的接口和具体类,定义为了不同的角色,这是我不赞同的地方。我认为他们应该属于同一个角色,只不过有的是接口,有的是类而已。

代理角色(Proxy role)

既然是代理模式,代理角色当然是代理模式的核心,对于代理角色的,认为它至少有两个重要职责。一个是自己的业务逻辑,代理角色通常是用来增强被代理角色的功能,或者限制对被代理角色的访问,这些都属于它自身的业务逻辑。

另一个,就是管理它与被代理角色的关系,代理角色最终还是要调用被代理类的,所以如何建立它与被代理角色的关系,非常重要。

在静态代理模式中,我们在代理类中,把被代理对象定义为它的属性,而在动态代理中,这种关系更为复杂,需要JDK或者spring aop模块帮助我们完成。

所以,我认为代理角色,根据它的功能,可以分为管理型代理对象和业务型代理对象。

管理型代理对象:通常由框架提供,我们不用操心。

业务型代理对象:用来实现代理对象自身的业务,这个我们程序员需要关心,框架无法给我们提供。

客户方角色(client role)

这个不用多说,每个设计模式都有,对于客户方角色,需要关注的就是,它是和谁交互的,很显然,在代理模式中,它是和代理角色交换的。

交互

交互,是指设计模式中,各种角色是如何交互的,一般用UML中的序列图,活动图来表示。简单的说就是角色之间是如何配合,完成设计模式的使命的。

在代理模式中,交互还是比较简单的,客户方角色调用代理方角色,代理方角色调用原角色。



效果

使用该设计模式之后,达到了什么效果,有何意义,当然,也可以说说它的缺点,或者风险。 从我们前面的案例可以看出,代理模式达到了以下效果:

  • 在不改变真实角色的情况下,增强其功能或控制其访问。

  • 能够让客户方使用代理角色访问真实角色,从而简化了客户方的操作。

spring AOP模块

提到spring框架,大家往往能想到的,是两个比较重要的模式,IOC和AOP,而AOP的背后,则是代理模式。

我们的重点是代理模式,所以关于AOP相关的内容,这里就不展开讲解了。如果你对代理模式能有一个很好的理解,在学习aop复杂多样的各种概念时,将会非常简单。

我们的第四版代码,就是基于spring aop实现的,里面使用的是前置通知。

第四版代码:基于spring aop的动态代理


测试代码:


运行结果

下期预告

下期,我将开始分享各种设计模式的评比以及知名开源软件中的设计模式。

极客架构师——专注架构师成长。 

我们下期见。




Copyright © 2024 aigcdaily.cn  北京智识时代科技有限公司  版权所有  京ICP备2023006237号-1