代理模式(权限、远程调用、延迟加载、日志和缓存)

news/2024/9/17 8:52:22/ 标签: 代理模式, java

1、权限保护代理模式

使用 代理模式 实现一个“干饭村约会系统服务”的示例,能够通过代理控制对实际对象(比如用户的约会资料)访问、保护隐私、限制不正当操作等。

需求分析:

  1. 用户(Person):干饭村的用户有自己的个人资料(如名字、性别、兴趣等),他们可以查看自己的和其他人的资料,并且可以修改自己的资料。
  2. 代理:分为两种代理:
    • 自己代理(Owner Proxy):允许用户查看和修改自己的资料。
    • 其他人代理(Non-Owner Proxy):允许其他人查看用户的资料,但不允许修改。
  3. 约会系统:负责管理用户资料的创建、展示和操作,使用代理控制访问权限。

解决方案:

  • 代理模式:通过创建代理对象来控制对实际对象的访问。我们创建不同的代理类,分别控制用户本人和其他人对其资料的操作权限。

代码实现

1. 创建 Person 接口

定义用户基本操作的接口。

java">public interface Person {String getName();String getGender();String getInterests();int getHotOrNotRating();void setName(String name);void setGender(String gender);void setInterests(String interests);void setHotOrNotRating(int rating);
}
2. 实现 PersonImpl 类(实际的用户类)

这是实际的用户实现,存储用户的资料。

java">public class PersonImpl implements Person {private String name;private String gender;private String interests;private int rating;private int ratingCount = 0;@Overridepublic String getName() {return name;}@Overridepublic String getGender() {return gender;}@Overridepublic String getInterests() {return interests;}@Overridepublic int getHotOrNotRating() {if (ratingCount == 0) return 0;return rating / ratingCount;}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic void setGender(String gender) {this.gender = gender;}@Overridepublic void setInterests(String interests) {this.interests = interests;}@Overridepublic void setHotOrNotRating(int rating) {this.rating += rating;ratingCount++;}
}
3. 创建代理 OwnerProxyNonOwnerProxy
3.1 OwnerProxy 类(自己代理)

用户只能修改自己的资料,但不能为自己打分。

java">public class OwnerProxy implements Person {private Person person;public OwnerProxy(Person person) {this.person = person;}@Overridepublic String getName() {return person.getName();}@Overridepublic String getGender() {return person.getGender();}@Overridepublic String getInterests() {return person.getInterests();}@Overridepublic int getHotOrNotRating() {return person.getHotOrNotRating();}@Overridepublic void setName(String name) {person.setName(name);}@Overridepublic void setGender(String gender) {person.setGender(gender);}@Overridepublic void setInterests(String interests) {person.setInterests(interests);}@Overridepublic void setHotOrNotRating(int rating) {throw new UnsupportedOperationException("You cannot rate yourself.");}
}
3.2 NonOwnerProxy 类(其他人代理)

其他人可以查看用户的资料并为其打分,但不能修改资料。

java">public class NonOwnerProxy implements Person {private Person person;public NonOwnerProxy(Person person) {this.person = person;}@Overridepublic String getName() {return person.getName();}@Overridepublic String getGender() {return person.getGender();}@Overridepublic String getInterests() {return person.getInterests();}@Overridepublic int getHotOrNotRating() {return person.getHotOrNotRating();}@Overridepublic void setName(String name) {throw new UnsupportedOperationException("You cannot change someone else's name.");}@Overridepublic void setGender(String gender) {throw new UnsupportedOperationException("You cannot change someone else's gender.");}@Overridepublic void setInterests(String interests) {throw new UnsupportedOperationException("You cannot change someone else's interests.");}@Overridepublic void setHotOrNotRating(int rating) {person.setHotOrNotRating(rating);}
}
4. 创建 PersonProxyFactory 工厂类

用于创建不同类型的代理。

java">public class PersonProxyFactory {public static Person getOwnerProxy(Person person) {return new OwnerProxy(person);}public static Person getNonOwnerProxy(Person person) {return new NonOwnerProxy(person);}
}
5. 测试代码

展示如何使用代理模式控制用户资料的访问权限。

java">public class DatingSystemTest {public static void main(String[] args) {Person joe = new PersonImpl();joe.setName("Joe");joe.setGender("Male");joe.setInterests("Eating, Cooking");// 作为自己访问Person ownerProxy = PersonProxyFactory.getOwnerProxy(joe);System.out.println("Name: " + ownerProxy.getName());ownerProxy.setInterests("Gaming, Running"); // 修改成功try {ownerProxy.setHotOrNotRating(10); // 无法为自己打分} catch (Exception e) {System.out.println("Cannot rate yourself.");}// 作为他人访问Person nonOwnerProxy = PersonProxyFactory.getNonOwnerProxy(joe);System.out.println("Name: " + nonOwnerProxy.getName());try {nonOwnerProxy.setInterests("Hiking"); // 无法修改他人资料} catch (Exception e) {System.out.println("Cannot modify interests.");}nonOwnerProxy.setHotOrNotRating(8); // 允许为他人打分System.out.println("Rating: " + nonOwnerProxy.getHotOrNotRating());}
}

输出结果:

Name: Joe
Cannot rate yourself.
Name: Joe
Cannot modify interests.
Rating: 8

代码解释:

  1. Person 接口:定义了用户的基本操作,如获取和设置姓名、性别、兴趣以及打分等功能。
  2. PersonImpl:用户的实际实现类,存储用户的基本信息。
  3. 代理类
    • OwnerProxy:允许用户修改自己的资料,但不允许为自己打分。
    • NonOwnerProxy:允许其他人查看用户的资料并为其打分,但不允许修改资料。
  4. PersonProxyFactory:通过该工厂类,可以为用户生成自己代理(OwnerProxy)或他人代理(NonOwnerProxy)。
  5. DatingSystemTest:测试代理模式的功能,展示不同代理如何控制对用户资料的访问。

代理模式的优点:

  • 控制访问:可以为不同的用户提供不同的权限。比如用户自己可以修改资料,而其他人只能查看并打分。
  • 保护敏感信息:通过代理可以防止不正当操作,保护用户隐私和资料安全。
  • 灵活性:代理类可以很容易地根据需求调整行为。

相关解释

代理模式 (Proxy Pattern) 详细解释

1. 代理模式的概念

代理模式为其他对象提供一个代理或占位符来控制对这个对象的访问。可以将代理模式理解为通过代理类来间接访问目标对象,从而可以增加一些额外的功能,如权限控制、延迟加载、日志记录等。

2. 代理模式的结构
  • Subject 接口:定义了目标对象和代理类的通用接口,确保它们具有一致的行为,使得客户端可以通过接口与真实对象或代理对象进行交互。

  • RealSubject 真实主题类:实际需要访问的对象,代理模式的目标对象,完成实际的操作和功能。

  • Proxy 代理类:代理对象,控制对真实对象的访问。代理类可以在访问真实对象之前或之后增加额外的行为,比如权限检查、日志记录、缓存等。

3. 代理模式的应用场景
  • 远程代理:为一个对象在不同地址空间提供局部代理。比如 RMI(Remote Method Invocation)中使用远程代理。
  • 虚拟代理:在需要时才创建复杂的对象。比如在需要时才加载图片等资源。
  • 保护代理:控制对目标对象的访问权限,确保只有授权用户才能进行某些操作。
  • 缓存代理:为目标对象的操作提供结果缓存,以便多次调用时减少重复计算。
  • 智能引用代理:在访问对象时增加一些附加操作,比如引用计数、对象锁定等。

代理模式的类型

  1. 远程代理:代理对象在客户端上本地表示,而真实对象在远程服务器上执行。客户端调用代理对象方法,代理通过网络通信调用远程对象的方法,典型场景是分布式系统。

  2. 虚拟代理:用于延迟创建代价高的对象或在创建对象时提供一些开销优化。比如大型图片只有在显示时才加载。

  3. 保护代理:控制对对象的访问权限,保护敏感操作。保护代理可以确保调用者是否有足够的权限访问对象的某些功能。

  4. 智能引用代理:代理会做额外的操作,比如引用计数、资源管理、缓存等。每次访问对象时,代理可以自动处理某些额外的任务。

干饭村约会系统中的代理模式

4. 干饭村约会系统代理模式详解

在这个场景中,我们使用保护代理来控制对用户个人资料的访问权限。具体来说,系统根据不同的用户身份(是本人还是他人)提供不同的访问代理类。

4.1 核心类结构
  • Person 接口:定义了用户的基本信息和操作,包括获取和设置姓名、性别、兴趣以及打分等操作。Person 接口相当于代理模式中的 Subject,表示所有操作的接口规范。

  • PersonImpl:用户的实际实现类,它包含了用户的基本资料信息。这个类相当于代理模式中的 RealSubject,是真正存储和处理用户资料的类。

  • OwnerProxy:为用户自己提供的代理类,它允许用户修改自己的资料,但不允许自己为自己打分。这个代理类控制对用户的实际对象(PersonImpl)的访问权限。

  • NonOwnerProxy:为其他用户提供的代理类,它允许其他用户查看并打分,但不允许他们修改用户的资料。它限制了其他用户对目标对象的访问。

4.2 功能解析
  • 自己代理(OwnerProxy:该代理类为用户本人提供服务,它允许用户查看和修改自己的个人信息,但限制了用户为自己打分。这是为了防止用户自己提升自己的受欢迎程度(HotOrNotRating),从而保持评分的公正性。

  • 他人代理(NonOwnerProxy:该代理类为其他用户提供服务,它允许其他用户查看用户的资料,并且可以为其打分(表示好感度或受欢迎度)。但是,该代理类限制了其他用户对个人信息的修改权限,防止篡改资料。

4.3 行为细节
  • 代理行为:当客户端调用代理对象的方法时(例如调用 setHotOrNotRating()setInterests()),代理对象会首先检查是否具有执行该操作的权限。如果有权限,它会将操作转发给真实的 PersonImpl 对象,否则会抛出 UnsupportedOperationException 异常。

  • 透明代理:代理类实现了和真实对象相同的接口 Person,使得客户端代码无须关心它是直接操作的真实对象还是通过代理对象操作,确保客户端代码的一致性和透明性。

代理模式的优点和适用性

5. 优点
  1. 控制访问代理模式能够灵活地控制对真实对象的访问,比如干饭村约会系统中的访问权限控制。

  2. 增加额外功能:代理类可以在访问真实对象之前或之后执行一些额外的功能,比如权限校验、日志记录、性能监控、结果缓存等。

  3. 解耦客户端和实际对象代理模式通过代理对象将客户端与真实对象隔离,使得真实对象的更改对客户端的影响最小。

  4. 支持远程调用或延迟加载代理模式允许远程调用或者延迟加载,当对象创建代价较高时,代理可以帮助进行优化。

6. 缺点
  1. 增加复杂性代理模式增加了系统的复杂性和间接性,尤其是在系统中有多个代理层时,可能导致代码难以维护和调试。

  2. 性能开销代理模式引入了额外的间接调用,可能带来一定的性能开销,特别是在远程代理或虚拟代理中,网络延迟和额外处理可能会影响性能。

7. 适用场景
  1. 权限控制:需要对某些方法调用进行权限控制,比如干饭村的约会系统中,不同的用户对其他人的资料有不同的操作权限。

  2. 远程调用:当对象位于不同的地址空间时,可以使用远程代理模式来处理分布式系统中的通信。

  3. 延迟加载:当对象创建代价较高时,使用虚拟代理可以在需要时才创建对象,避免不必要的开销。

  4. 日志和缓存:可以使用代理模式在真实对象方法调用前后记录日志或缓存结果,提高性能。

代理模式的总结

代理模式通过代理对象控制对真实对象的访问,既可以提高系统的安全性和可控性,也可以减少客户端和真实对象之间的耦合。干饭村的约会系统通过代理模式保护用户的个人资料,保证只有自己能够修改资料,而其他人只能查看和打分,从而增强系统的安全性和公平性。

2、远程调用

远程调用(Remote Method Invocation, RMI)是一种允许对象在不同的 Java 虚拟机中调用方法的机制,适用于分布式系统。在 RMI 中,客户端可以通过代理(代理模式)调用远程服务器上的方法,而不需要知道方法的具体实现。

Java RMI 示例

我们将通过一个简单的 Java RMI 示例来展示如何进行远程调用。假设我们有一个远程接口 Calculator,客户端可以调用远程服务器上的加法和减法操作。

步骤:
  1. 定义远程接口 Calculator
  2. 实现远程接口
  3. 启动 RMI 注册表并绑定远程对象
  4. 编写客户端来调用远程对象

1. 创建远程接口 Calculator

远程接口需要继承 java.rmi.Remote 接口,并声明所有的远程方法可能抛出的 RemoteException

java">import java.rmi.Remote;
import java.rmi.RemoteException;public interface Calculator extends Remote {// 声明远程方法int add(int a, int b) throws RemoteException;int subtract(int a, int b) throws RemoteException;
}

2. 实现远程接口

远程对象的实现类需要继承 UnicastRemoteObject,这是为了使对象可以被远程调用。

java">import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;public class CalculatorImpl extends UnicastRemoteObject implements Calculator {// 必须定义一个构造器,并抛出 RemoteExceptionprotected CalculatorImpl() throws RemoteException {super();}@Overridepublic int add(int a, int b) throws RemoteException {return a + b;}@Overridepublic int subtract(int a, int b) throws RemoteException {return a - b;}
}

3. 创建服务器端

服务器端需要启动 RMI 注册表,并将远程对象绑定到注册表中,使客户端能够通过注册表获取该远程对象。

java">import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;public class CalculatorServer {public static void main(String[] args) {try {// 启动 RMI 注册表,默认端口是 1099LocateRegistry.createRegistry(1099);// 创建远程对象Calculator calculator = new CalculatorImpl();// 绑定远程对象到 RMI 注册表Naming.rebind("rmi://localhost:1099/CalculatorService", calculator);System.out.println("Calculator Service is running...");} catch (RemoteException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}
}

4. 创建客户端

客户端通过 RMI 注册表查找远程对象,并调用其方法。

java">import java.rmi.Naming;public class CalculatorClient {public static void main(String[] args) {try {// 查找远程对象Calculator calculator = (Calculator) Naming.lookup("rmi://localhost:1099/CalculatorService");// 调用远程方法int sum = calculator.add(5, 3);int difference = calculator.subtract(10, 4);System.out.println("Sum: " + sum);System.out.println("Difference: " + difference);} catch (Exception e) {e.printStackTrace();}}
}

5. 编译和运行

编译步骤:
  1. 先编译所有类:

    javac Calculator.java CalculatorImpl.java CalculatorServer.java CalculatorClient.java
    
  2. 运行 RMI 注册表:
    在命令行中输入:

    rmiregistry
    

    注意:RMI 注册表必须在服务器启动之前启动。否则,服务器端程序将无法绑定远程对象。

  3. 启动服务器:
    在命令行中运行:

    java CalculatorServer
    
  4. 运行客户端:
    在客户端机器或同一台机器上,运行客户端程序:

    java CalculatorClient
    

6. 运行结果:

当客户端成功调用远程方法时,输出类似如下:

Sum: 8
Difference: 6

解释:

  • Calculator 接口:定义了远程服务的行为,客户端和服务器都需要这个接口。
  • CalculatorImpl:这是远程服务的实际实现,服务器将它绑定到 RMI 注册表中。
  • 服务器端:通过 LocateRegistry.createRegistry() 创建一个 RMI 注册表,然后使用 Naming.rebind() 方法将远程对象绑定到特定名称(在这个例子中是 CalculatorService)。
  • 客户端:通过 Naming.lookup() 查找远程对象,然后调用远程对象的方法。RMI 底层处理网络通信,客户端可以像调用本地方法一样调用远程方法。

总结:

  • RMI 的核心思想是让客户端可以透明地调用位于远程服务器上的方法,就像调用本地对象的方法一样。
  • 在实现时,接口用于定义服务契约,客户端和服务器都依赖接口进行通信。
  • 代理模式在 RMI 中的应用体现在客户端通过代理对象(远程对象的存根)调用远程服务,而不是直接操作远程服务对象。

3、延迟加载

延迟加载代理模式(Virtual Proxy)是代理模式的一种,它用于推迟对昂贵资源的加载,直到真正需要时才初始化该资源。这种模式适用于那些初始化成本高但并非总是立即使用的对象,比如图像、文件等。

下面是一个基于延迟加载代理模式的 Java 示例:假设我们有一个图像浏览器,图像可能非常大,所以我们希望在需要显示时才加载图像。

示例场景:

  • Image 接口:定义图像的行为。
  • RealImage:实际的图像类,加载图像的过程可能比较耗时。
  • ImageProxy:代理类,只有在需要显示图像时才加载 RealImage
  • Client:客户端使用图像代理来控制加载。

代码实现

1. 定义 Image 接口

首先定义 Image 接口,包含一个 display() 方法,用于显示图像。

java">public interface Image {void display();
}
2. 创建 RealImage

RealImage 类是图像的实际实现类,加载过程可能比较耗时,比如从磁盘加载大文件。在构造函数中模拟加载过程。

java">public class RealImage implements Image {private String filename;// 模拟加载过程public RealImage(String filename) {this.filename = filename;loadImageFromDisk();}// 模拟图像从磁盘加载的操作private void loadImageFromDisk() {System.out.println("Loading image from disk: " + filename);try {Thread.sleep(3000); // 模拟耗时的加载} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic void display() {System.out.println("Displaying image: " + filename);}
}
3. 创建 ImageProxy 类(延迟加载代理)

代理类 ImageProxy 实现 Image 接口,但它不会立即加载图像,只有在调用 display() 方法时才初始化 RealImage

java">public class ImageProxy implements Image {private RealImage realImage;private String filename;public ImageProxy(String filename) {this.filename = filename;}@Overridepublic void display() {// 延迟加载 RealImage,只有在真正需要时才初始化if (realImage == null) {realImage = new RealImage(filename);}realImage.display();}
}
4. 测试客户端

客户端不直接与 RealImage 交互,而是通过代理 ImageProxy 访问图像。

java">public class Client {public static void main(String[] args) {Image image1 = new ImageProxy("photo1.jpg");Image image2 = new ImageProxy("photo2.jpg");// 图像还未被加载,此时不显示加载信息System.out.println("Images are created, but not loaded yet.");// 当需要显示图像时,图像才会被加载image1.display();  // 第一次显示图像,加载后再显示image1.display();  // 第二次显示图像,已经加载过,直接显示image2.display();  // 第一次显示图像,加载后再显示}
}

输出结果

Images are created, but not loaded yet.
Loading image from disk: photo1.jpg
Displaying image: photo1.jpg
Displaying image: photo1.jpg
Loading image from disk: photo2.jpg
Displaying image: photo2.jpg

代码分析

  1. Image 接口:定义了图像的基本行为 display(),它是 RealImageImageProxy 共同的接口。

  2. RealImage:这是实际的图像实现类,包含从磁盘加载图像的模拟过程(耗时操作),并实现了 display() 方法来显示图像。

  3. ImageProxy:代理类实现了 Image 接口,但它控制 RealImage 的创建和加载。只有在 display() 方法被调用时,ImageProxy 才会创建 RealImage 对象并调用它的 display() 方法。这种设计避免了不必要的图像加载,从而提高了性能。

  4. 延迟加载:图像只有在需要显示时才会加载,这就是延迟加载的特点。如果客户端从不调用 display() 方法,图像也不会被加载。

延迟加载代理模式的优点

  1. 性能优化:通过延迟加载减少不必要的初始化操作,特别是在加载过程非常耗时的情况下。
  2. 资源节省:当图像、文件或其他资源较大或较多时,代理模式可以有效节省内存和CPU资源。
  3. 透明性:客户端代码不需要改变,仍然使用统一的 Image 接口,代理类控制实际的加载过程。

适用场景

  • 当对象的创建开销非常大,且并非每次都会使用到时。
  • 当需要延迟加载某些资源,比如大文件、图像、数据库连接等。
  • 在某些情况下,可以在需要时才执行某些昂贵的操作,而不是在对象初始化时立即执行。

通过这个示例,延迟加载代理模式 通过代理对象在需要时才实例化真正的对象,避免了不必要的资源浪费,提高了系统性能。

4、日志和缓存代理模式

日志代理模式缓存代理模式都是代理模式的扩展,用于在不改变原始类代码的情况下添加额外的功能。日志代理用于记录方法调用日志,而缓存代理用于减少重复计算或数据获取,提升性能。

下面是基于 日志代理模式缓存代理模式 的示例代码,展示如何使用代理模式记录方法调用日志和缓存计算结果。

示例场景

假设我们有一个简单的计算器类,进行一些复杂计算。我们将使用日志代理记录每次计算的调用,使用缓存代理存储计算结果以避免重复计算。

1. 创建 Calculator 接口

定义一个基本的 Calculator 接口,包含一个用于进行复杂计算的方法 compute()

java">public interface Calculator {int compute(int a, int b);
}

2. 实现 Calculator

RealCalculator 类是计算器的实际实现类,它执行复杂的计算操作。在此示例中,假设 compute() 方法执行复杂的乘法运算。

java">public class RealCalculator implements Calculator {@Overridepublic int compute(int a, int b) {// 假设这是一个耗时的计算System.out.println("Performing complex calculation...");return a * b;}
}

3. 日志代理类 LoggingProxy

LoggingProxy 代理类用于记录每次调用 compute() 方法时的日志信息。该代理类会在方法调用前后打印日志,但不改变原始计算逻辑。

java">public class LoggingProxy implements Calculator {private Calculator calculator;public LoggingProxy(Calculator calculator) {this.calculator = calculator;}@Overridepublic int compute(int a, int b) {System.out.println("Logging: About to compute (" + a + ", " + b + ")");int result = calculator.compute(a, b);System.out.println("Logging: Computation result is " + result);return result;}
}

4. 缓存代理类 CachingProxy

CachingProxy 类用于缓存先前的计算结果,以避免重复计算。它使用一个 Map 来存储输入参数和对应的计算结果。

java">import java.util.HashMap;
import java.util.Map;public class CachingProxy implements Calculator {private Calculator calculator;private Map<String, Integer> cache = new HashMap<>();public CachingProxy(Calculator calculator) {this.calculator = calculator;}@Overridepublic int compute(int a, int b) {String key = a + "," + b;// 检查缓存中是否已有该计算结果if (cache.containsKey(key)) {System.out.println("Cache hit for (" + a + ", " + b + ")");return cache.get(key);}// 如果缓存中没有,进行计算并存入缓存int result = calculator.compute(a, b);cache.put(key, result);return result;}
}

5. 测试客户端

在客户端中,我们先创建一个 RealCalculator 实例,然后通过代理类进行调用。

java">public class Client {public static void main(String[] args) {// 创建实际的计算器对象Calculator realCalculator = new RealCalculator();// 包装成日志代理Calculator loggingCalculator = new LoggingProxy(realCalculator);// 包装成缓存代理Calculator cachingCalculator = new CachingProxy(loggingCalculator);// 执行一些计算操作System.out.println("First calculation:");System.out.println("Result: " + cachingCalculator.compute(3, 4));System.out.println("\nSecond calculation (same values, should use cache):");System.out.println("Result: " + cachingCalculator.compute(3, 4));System.out.println("\nThird calculation (different values):");System.out.println("Result: " + cachingCalculator.compute(5, 6));}
}

6. 运行结果

First calculation:
Logging: About to compute (3, 4)
Performing complex calculation...
Logging: Computation result is 12
Result: 12Second calculation (same values, should use cache):
Cache hit for (3, 4)
Result: 12Third calculation (different values):
Logging: About to compute (5, 6)
Performing complex calculation...
Logging: Computation result is 30
Result: 30

代码解释

  1. RealCalculator:这是实际的计算类,它执行真实的计算操作。每次调用 compute() 方法时,它都会进行一次“复杂”的计算(在此示例中为乘法)。

  2. LoggingProxy:它是日志代理类,用于记录计算的调用过程。在调用 compute() 方法之前,代理类会记录日志,表示即将开始计算;在调用方法之后,代理类会记录计算结果。

  3. CachingProxy:它是缓存代理类,负责缓存先前的计算结果,避免重复计算。如果相同的输入参数之前已经计算过,代理类会直接返回缓存中的结果,而不再调用实际的计算逻辑。

  4. 客户端:客户端使用代理类来操作计算器对象,而不直接操作 RealCalculator。客户端并不关心计算器是直接执行的还是通过代理执行的,只关心计算的结果。

总结

  • 日志代理模式 可以在不修改原始类的前提下增加方法调用日志记录,方便调试和监控。
  • 缓存代理模式 可以通过缓存计算结果来提高性能,避免重复计算,适用于昂贵的计算任务。
  • 代理模式通过使用代理类包装原始类,增强了系统的灵活性,同时保持了客户端的调用方式不变。

http://www.ppmy.cn/news/1522800.html

相关文章

自我指导:提升语言模型自我生成指令的能力

人工智能咨询培训老师叶梓 转载标明出处 传统的语言模型&#xff0c;尤其是经过指令微调的大型模型&#xff0c;虽然在零样本&#xff08;zero-shot&#xff09;任务泛化上表现出色&#xff0c;但它们高度依赖于人类编写的指令数据。这些数据往往数量有限、多样性不足&#xf…

uniapp+vue+ts开发中使用signalR实现客户端和服务器通讯

SignalR SignalR 面向 ES6。 对于不支持 ES6 的浏览器&#xff0c;请将库转译为 ES5。 SignalR 支持以下用于处理实时通信的技术&#xff08;按正常回退的顺序&#xff09;&#xff1a; WebSocketsServer-Sent Events长轮询SignalR 自动选择服务器和客户端能力范围内的最佳传输…

如何在极狐GitLab中添加 SSH Key?

本文分享如何生成 SSH Key 并添加到极狐GitLab 中&#xff0c;然后用 SSH Key 进行代码拉取。 极狐GitLab 是 GitLab 在中国的发行版&#xff0c;可以私有化部署&#xff0c;对中文的支持非常友好&#xff0c;是专为中国程序员和企业推出的企业级一体化 DevOps 平台&#xff0…

路由器的固定ip地址是啥意思?固定ip地址有什么好处

‌在当今数字化时代&#xff0c;‌路由器作为连接互联网的重要设备&#xff0c;‌扮演着举足轻重的角色。‌其中&#xff0c;‌路由器的固定IP地址是一个常被提及但可能让人困惑的概念。‌下面跟着虎观代理小二一起将深入探讨路由器的固定IP地址的含义&#xff0c;‌揭示其背后…

图文解析保姆级教程:Postman专业接口测试工具的安装和基本使用

文章目录 1. 引入2. 介绍3. 安装4. 使用 此教程摘选自我的笔记&#xff1a;黑马JavaWeb开发笔记16——请求&#xff08;postman、简单参数、实体参数、RequestParam映射&#xff09;想要详细了解更多有关请求各种参数介绍的知识可以移步此篇笔记。 1. 引入 在当前最为主流的开…

营养餐共享网站:项目规划Plan1

缘起 一些小众的项目&#xff0c;可能还没有较好的网站服务。一些APP项目&#xff0c;受限于支付宝和微信等的限制&#xff0c;只能很简单的在搜索框查找&#xff0c;不能像网站那样在公开引擎上搜索&#xff0c;那个范围更广&#xff0c;搜索到的结果更多。 所以我们想做一个…

数据结构代码集训day15(适合考研、自学、期末和专升本)

本份题目来自B站up&#xff1a;白话拆解数据结构 今日题目如下; &#xff08;1&#xff09;编写算法&#xff0c;实现十进制转十六进制&#xff1b; &#xff08;2&#xff09;汉诺塔&#xff08;Hanoi Tower&#xff09;&#xff0c;又称河内塔&#xff0c;源于印度一个古老…

TCP协议多进程多线程并发服务器

TCP多进程多线程并发服务器 1.多进程并发服务器 #include <myhead.h>#define SERPORT 6666 #define SERIP "192.168.0.136" #define BLACKLOG 10void hande(int a) {if(aSIGCHLD){while(waitpid(-1,NULL,WNOHANG)!-1);//回收僵尸进程} }int main(int argc, c…

深度学习(一)-感知机+神经网络+激活函数

深度学习概述 深度学习的特点 优点 性能更好 不需要特征工程 在大数据样本下有更好的性能 能解决某些传统机器学习无法解决的问题 缺点 小数据样本下性能不如机器学习 模型复杂 可解释性弱 深度学习与传统机器学习相同点 深度学习、机器学习是同一问题不同的解决方法 …

Gin自定义校验函数

在Web开发中&#xff0c;数据验证是确保用户输入符合预期格式的关键步骤。Gin框架通过集成go-playground/validator包&#xff0c;提供了强大的数据验证功能。除了内置的验证规则&#xff0c;Gin还支持自定义验证函数&#xff0c;这使得我们可以针对特定的业务需求灵活地定义验…

GitHub每日最火火火项目(9.8)

项目名称&#xff1a;polarsource / polar 项目介绍&#xff1a;polar 是一个开源的项目&#xff0c;它是 Lemon Squeezy 的替代方案&#xff0c;并且具有更优惠的价格。这个项目的目标是让开发者能够在自己热爱的编码工作中获得报酬。它为开发者提供了一种新的选择&#xff0c…

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式&#xff08;Singleton Pattern&#xff09; 确保一个类只有一个实例&#xff0c;并提供一个全局访问点。 示例代码&#xff1a; class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance this;this.data []…

数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树

文章目录 树与二叉树的应用1.哈夫曼树与哈夫曼曼编码1.1 带权路径长度1.2 哈夫曼树1.2.1 哈夫曼树的构造1.3 哈夫曼编码 2.并查集2.1 并查集的三要素2.1.1 并查集的逻辑结构2.1.2 并查集的存储结构 2.2 并查集的优化2.2.1 初步优化&#xff08;并操作优化&#xff09;2.2.2 终极…

mybatis官方仓库-常用的仓库都有哪些作用

在GitHub上&#xff0c;MyBatis组织下的37个仓库主要涵盖了MyBatis框架的各个方面&#xff0c;包括但不限于核心框架、插件、工具、示例以及与其他技术的集成等。以下是对这些仓库功能的大致分类和描述&#xff1a; MyBatis 核心项目 mybatis-3&#xff1a;这是MyBatis的核心…

C语言深度剖析--不定期更新的第五弹

const关键字 来看一段代码&#xff1a; #include <stdio.h> int main() {int a 10;a 20;printf("%d\n", a);return 0; }运行结果如下&#xff1a; 接下来我们在上面的代码做小小的修改&#xff1a; #include <stdio.h> int main() {const int a 1…

2024数学建模国赛ABCDE题选题分析及初步思路

高教社杯全国大学生数学建模竞赛&#xff08;以下简称“国赛”&#xff09;是面向全国大学生的一项重要赛事&#xff0c;旨在培养学生的数学建模能力、团队合作能力和科学研究能力。近年来&#xff0c;国赛的参赛人数和比赛难度不断提升&#xff0c;对参赛者的数学建模能力提出…

C++复习day05

类和对象 1. 面向对象和面向过程的区别是什么&#xff1f;&#xff08;开放性问题&#xff09; 1. **抽象级别**&#xff1a;- **面向对象**&#xff1a;以对象&#xff08;数据和方法的集合&#xff09;为中心&#xff0c;强调的是数据和行为的封装。- **面向过程**&#xf…

探索fastFM:Python中的高效推荐系统库

文章目录 &#x1f680; 探索fastFM&#xff1a;Python中的高效推荐系统库背景&#xff1a;为何选择fastFM&#xff1f;快照&#xff1a;fastFM是什么&#xff1f;安装指南&#xff1a;如何将fastFM加入你的项目&#xff1f;快速入门&#xff1a;五个基础函数的使用实战演练&am…

C语言第二周课

目录 引言: 一、数据类型大小及分类 (1)计算机中常用存储单位 (2)整体介绍一下C语言的数据类型分类。 (3)下面是我们本节课要学的基本内容----常用的数据类型 二、 数据类型的数值范围 三、打印输出类型 数据类型打印示例: 引言: 我们常常在写C语言程序时&#xff0c;总…

滚雪球学MyBatis-Plus(13):测试与部署

前言 在上期内容中&#xff0c;我们深入探讨了 MyBatis Plus 的高级功能&#xff0c;包括自定义 SQL 注解、批量操作以及数据加密与解密。这些功能极大地提高了开发效率&#xff0c;并增强了数据操作的灵活性和安全性。 本期内容将重点介绍 MyBatis Plus 的测试与部署。我们将…