目录¶
1. 代理模式简介¶
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。通过代理对象,客户端可以在不直接访问目标对象的情况下,间接地执行目标对象的方法。代理模式在实际应用中常用于控制对象的访问、延迟对象的创建、记录对象的访问日志等。
关键点:
代理对象:代表目标对象,控制对目标对象的访问。
目标对象:被代理的真实对象,实际执行业务逻辑。
透明性:代理对象与目标对象实现相同的接口,客户端无需感知代理的存在。
2. 代理模式的意图¶
代理模式的主要目的是:
控制对目标对象的访问:通过代理对象,可以在访问目标对象前后执行额外的操作,如权限检查、日志记录等。
延迟对象的创建:在目标对象创建成本较高时,可以通过代理对象实现按需创建,优化资源使用。
隐藏目标对象的实现细节:客户端无需了解目标对象的具体实现,只需通过代理接口进行交互。
增强目标对象的功能:通过代理对象可以为目标对象添加新的功能,而无需修改目标对象本身。
3. 代理模式的结构¶
3.1. 结构组成¶
代理模式主要由以下四个角色组成:
Subject(主题接口):定义了目标对象和代理对象的共同接口,确保代理对象可以替代目标对象。
RealSubject(真实主题):实现了主题接口,是真正执行业务逻辑的对象。
Proxy(代理对象):实现了主题接口,持有真实主题对象的引用,并控制对真实主题的访问。
Client(客户端):通过主题接口与代理对象交互,无需直接访问真实主题对象。
角色关系:
Proxy 持有一个 RealSubject 对象的引用。
Proxy 和 RealSubject 都实现了 Subject 接口。
Client 通过 Subject 接口与 Proxy 交互。
3.2. UML类图¶
以下是代理模式的简化UML类图:
+----------------+ +----------------+
| Client | | Subject |
+----------------+ +----------------+
| | | + request() |
| | +----------------+
| | ^
| | |
| | +----------------+
| | | RealSubject |
| | +----------------+
| | | + request() |
| | +----------------+
| |
| | +----------------+
| | | Proxy |
| | +----------------+
| | | - realSubject |
| | +----------------+
| | | + request() |
| | +----------------+
+----------------+
说明:
Client 调用 Subject 接口的
request()
方法。Proxy 实现了 Subject 接口,并持有 RealSubject 对象的引用。
Proxy 在
request()
方法中,可以在调用 RealSubject 的request()
方法之前或之后执行额外的操作。
4. 代理模式的实现¶
以下示例将展示如何在Java和Python中实现代理模式。
4.1. Java 实现示例¶
以下是一个使用代理模式实现图片加载的示例,其中Image作为主题接口,RealImage作为真实主题,ProxyImage作为代理对象。
// Subject接口
public interface Image {
void display();
}
// RealSubject类
public class RealImage implements Image {
private String filename;
public RealImage(String filename){
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk(){
System.out.println("Loading image: " + filename);
}
@Override
public void display(){
System.out.println("Displaying image: " + filename);
}
}
// Proxy类
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename){
this.filename = filename;
}
@Override
public void display(){
if(realImage == null){
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 客户端代码
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image1 = new ProxyImage("test_10mb.jpg");
Image image2 = new ProxyImage("test_20mb.jpg");
// 图像将从磁盘加载
image1.display();
System.out.println("");
// 图像不需要从磁盘加载
image1.display();
System.out.println("");
// 图像将从磁盘加载
image2.display();
System.out.println("");
// 图像不需要从磁盘加载
image2.display();
}
}
输出:
Loading image: test_10mb.jpg
Displaying image: test_10mb.jpg
Displaying image: test_10mb.jpg
Loading image: test_20mb.jpg
Displaying image: test_20mb.jpg
Displaying image: test_20mb.jpg
说明:
RealImage 在构造时加载图像,
display()
方法显示图像。ProxyImage 在首次调用
display()
时创建 RealImage 对象,实现延迟加载。客户端通过 Image 接口与 ProxyImage 交互,无需直接访问 RealImage。
4.2. Python 实现示例¶
以下是使用代理模式实现图片加载的Python示例。
from abc import ABC, abstractmethod
# Subject接口
class Image(ABC):
@abstractmethod
def display(self):
pass
# RealSubject类
class RealImage(Image):
def __init__(self, filename):
self.filename = filename
self.load_from_disk()
def load_from_disk(self):
print(f"Loading image: {self.filename}")
def display(self):
print(f"Displaying image: {self.filename}")
# Proxy类
class ProxyImage(Image):
def __init__(self, filename):
self.filename = filename
self.real_image = None
def display(self):
if self.real_image is None:
self.real_image = RealImage(self.filename)
self.real_image.display()
# 客户端代码
def proxy_pattern_demo():
image1 = ProxyImage("test_10mb.jpg")
image2 = ProxyImage("test_20mb.jpg")
# 图像将从磁盘加载
image1.display()
print()
# 图像不需要从磁盘加载
image1.display()
print()
# 图像将从磁盘加载
image2.display()
print()
# 图像不需要从磁盘加载
image2.display()
if __name__ == "__main__":
proxy_pattern_demo()
输出:
Loading image: test_10mb.jpg
Displaying image: test_10mb.jpg
Displaying image: test_10mb.jpg
Loading image: test_20mb.jpg
Displaying image: test_20mb.jpg
Displaying image: test_20mb.jpg
说明:
RealImage 在初始化时加载图像,
display()
方法显示图像。ProxyImage 在首次调用
display()
时创建 RealImage 对象,实现延迟加载。客户端通过 Image 接口与 ProxyImage 交互,无需直接访问 RealImage。
5. 代理模式的适用场景¶
代理模式适用于以下场景:
需要控制对某个对象的访问:如在访问敏感资源时进行权限检查。
需要延迟对象的创建和初始化:如在需要时才创建大型对象,节省系统资源。
需要在对象访问前后执行额外的操作:如日志记录、缓存、性能监控等。
需要提供一个简化的接口:如为复杂子系统提供一个简单的访问接口。
需要远程访问对象:如在分布式系统中,通过代理对象实现远程方法调用。
示例应用场景:
虚拟代理:延迟创建和加载对象,如图片加载、虚拟列表等。
保护代理:控制对对象的访问权限,如安全控制、认证等。
远程代理:在不同地址空间中代表对象,实现远程调用。
智能引用代理:在访问对象时添加额外的操作,如计数、记录日志等。
6. 代理模式的优缺点¶
6.1. 优点¶
职责清晰:代理对象和目标对象各自承担不同的职责,代理负责控制访问,目标负责业务逻辑。
提高灵活性和可扩展性:通过代理对象可以在不修改目标对象的情况下,动态地为目标对象添加新功能。
隐藏目标对象的实现细节:客户端通过代理接口与目标对象交互,无需了解目标对象的具体实现。
节省资源:通过延迟加载或控制访问,可以节省系统资源,提高性能。
6.2. 缺点¶
增加系统复杂性:引入代理类会增加系统的类数量,可能使系统结构复杂。
可能影响性能:代理对象在调用目标对象的方法时会引入额外的层次,可能影响系统的性能,尤其是在代理层数较多时。
代理对象的维护:需要维护代理类和目标类的一致性,确保代理类正确地转发请求。
过度使用代理:如果不合理地使用代理模式,可能导致系统设计臃肿,难以维护。
7. 代理模式的实际应用实例¶
7.1. 虚拟代理¶
在需要延迟加载大型对象时,可以使用虚拟代理。例如,在图形用户界面中显示高分辨率图片时,可以先显示低分辨率的占位图,等用户查看时再加载高清图片。
示例:
// Java中的虚拟代理示例已在代理模式的实现部分展示。
7.2. 保护代理¶
在访问敏感资源时,可以使用保护代理进行权限检查。例如,文件系统中的文件访问控制,通过代理类检查用户权限后再允许访问文件。
示例:
// Subject接口
public interface Document {
void display();
}
// RealSubject类
public class RealDocument implements Document {
private String filename;
public RealDocument(String filename){
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk(){
System.out.println("Loading document: " + filename);
}
@Override
public void display(){
System.out.println("Displaying document: " + filename);
}
}
// Proxy类
public class ProxyDocument implements Document {
private RealDocument realDocument;
private String filename;
private User user;
public ProxyDocument(String filename, User user){
this.filename = filename;
this.user = user;
}
@Override
public void display(){
if(user.hasPermission("DISPLAY_DOCUMENT")){
if(realDocument == null){
realDocument = new RealDocument(filename);
}
realDocument.display();
} else {
System.out.println("Access denied. You do not have permission to display the document.");
}
}
}
// 用户类
public class User {
private String name;
private boolean hasPermission;
public User(String name, boolean hasPermission){
this.name = name;
this.hasPermission = hasPermission;
}
public boolean hasPermission(String permission){
// 简化权限检查逻辑
return hasPermission;
}
}
// 客户端代码
public class ProxyPatternDemo {
public static void main(String[] args) {
User userWithAccess = new User("Alice", true);
User userWithoutAccess = new User("Bob", false);
Document doc1 = new ProxyDocument("SecretDoc.pdf", userWithAccess);
Document doc2 = new ProxyDocument("SecretDoc.pdf", userWithoutAccess);
doc1.display(); // 允许访问
doc2.display(); // 拒绝访问
}
}
输出:
Loading document: SecretDoc.pdf
Displaying document: SecretDoc.pdf
Access denied. You do not have permission to display the document.
7.3. 远程代理¶
在分布式系统中,远程代理用于代表远程对象,实现远程方法调用。例如,Java RMI(Remote Method Invocation)中的远程代理,通过代理对象与远程服务进行通信。
示例:
// Java RMI 示例超出了本回答的范围,但可以参考相关Java RMI教程。
7.4. 智能引用代理¶
在访问对象时,添加额外的操作,如计数、日志记录等。例如,记录每次访问目标对象的方法调用次数。
示例:
// Subject接口
public interface Service {
void performOperation();
}
// RealSubject类
public class RealService implements Service {
@Override
public void performOperation(){
System.out.println("Performing operation in RealService.");
}
}
// Proxy类
public class ProxyService implements Service {
private RealService realService;
private int operationCount = 0;
@Override
public void performOperation(){
operationCount++;
System.out.println("Operation count: " + operationCount);
if(realService == null){
realService = new RealService();
}
realService.performOperation();
}
}
// 客户端代码
public class ProxyPatternDemo {
public static void main(String[] args) {
Service service = new ProxyService();
service.performOperation();
service.performOperation();
service.performOperation();
}
}
输出:
Operation count: 1
Performing operation in RealService.
Operation count: 2
Performing operation in RealService.
Operation count: 3
Performing operation in RealService.
8. 代理模式与其他模式的比较¶
8.1. 代理模式 vs. 装饰者模式¶
代理模式用于控制对目标对象的访问,代理对象与目标对象实现相同的接口,但代理负责在访问目标对象时执行额外的操作,如权限检查、延迟加载等。
装饰者模式用于动态地为对象添加职责,装饰者对象与目标对象实现相同的接口,通过组合方式为目标对象添加新功能。
关键区别:
目的不同:代理模式侧重于控制访问和管理对象,而装饰者模式侧重于功能的扩展和增强。
结构不同:代理通常只有一个代理类与目标类的关系,而装饰者可以有多个装饰者叠加。
8.2. 代理模式 vs. 适配器模式¶
代理模式用于控制对对象的访问,通过代理对象实现对目标对象的间接访问。
适配器模式用于接口转换,使得不兼容的接口能够协同工作,通过适配器类将一个接口转换为另一个接口。
关键区别:
目的不同:代理模式是为了控制访问和管理对象,适配器模式是为了兼容接口。
应用场景不同:代理模式用于需要对对象访问进行控制的场景,适配器模式用于需要整合不兼容接口的场景。
8.3. 代理模式 vs. 享元模式¶
代理模式用于控制对单个对象的访问,代理对象通常与目标对象一对一对应。
享元模式用于共享大量细粒度对象,通过享元工厂管理和共享对象,减少内存占用。
关键区别:
目的不同:代理模式侧重于控制访问和管理对象,享元模式侧重于对象的共享和复用。
应用场景不同:代理模式适用于需要控制访问和添加额外功能的单个对象,享元模式适用于需要管理和复用大量相似对象的场景。
8.4. 代理模式 vs. 策略模式¶
代理模式用于控制对对象的访问,通过代理对象实现对目标对象的间接访问。
策略模式用于定义一系列算法,并使它们可以相互替换,封装算法的变化。
关键区别:
目的不同:代理模式侧重于控制访问和管理对象,策略模式侧重于算法的选择和替换。
功能不同:代理模式通过代理对象控制对目标对象的访问,策略模式通过策略接口选择不同的算法实现。
9. 总结¶
代理模式(Proxy Pattern) 通过为其他对象提供一个代理对象,以控制对目标对象的访问,实现了对目标对象的间接访问和管理。代理模式适用于需要控制对象访问、延迟对象创建、记录访问日志等场景,特别是在系统需要对目标对象的访问进行额外控制或优化时。
关键学习点回顾:
理解代理模式的核心概念:通过代理对象控制对目标对象的访问,实现间接访问和管理。
掌握代理模式的结构:包括Subject、RealSubject、Proxy和Client之间的关系。
识别适用的应用场景:控制对象访问、延迟对象创建、记录日志、远程访问等。
认识代理模式的优缺点:职责清晰、提高灵活性和可扩展性,但增加系统复杂性、可能影响性能。
实际应用中的代理模式实例:虚拟代理、保护代理、远程代理、智能引用代理等。