代理模式(Proxy Pattern)是一种结构型设计模式,广泛应用于远程调用、访问控制、缓存优化等场景。
它通过为对象提供一个代理来控制对这个对象的访问。代理对象在客户端与目标对象之间起到一个中介的作用,客户端不直接与实际对象交互,而是通过代理对象来进行调用。代理对象可以在目标对象被调用之前或之后,添加额外的功能。代理模式通过这种方式来增强对象的灵活性,同时又不会对原有对象进行修改。这种设计思想非常符合开闭原则(Open/Closed Principle),有助于代码的扩展和维护。
代理模式的分类
- 远程代理(Remote Proxy):为位于不同地址空间的对象提供代理,通常是用于客户端与远程服务器之间的通信。通过代理对象隐藏实际网络连接的细节,使得客户端以为是在调用本地对象。
- 虚拟代理(Virtual Proxy):用于创建开销较大的对象。当对象的创建很耗资源且不一定需要时,虚拟代理在真正需要的时候才创建真实对象,并进行延迟加载。
- 保护代理(Protection Proxy):控制对目标对象的访问权限,通常用于限制一些特定用户或操作。
- 智能代理(Smart Proxy):为目标对象增加一些额外的功能,例如计数器、日志记录等。
代理模式的结构
代理模式通常包含以下几个角色:
- 抽象主题(Subject):定义代理类和真实对象类的接口,使得代理对象和真实对象可以互换。
- 真实主题(RealSubject):实现抽象主题,定义了代理对象所代表的真实对象。
- 代理主题(Proxy):实现抽象主题,并且持有一个真实主题对象的引用。代理主题可以在调用真实对象之前或者之后进行一些额外的处理。
类图
plaintext
Client
|
v
+-------------+ +-------------+
| Subject |<-------| Proxy |
+-------------+ +-------------+
^ |
| v
+-------------+ +-------------+
| RealSubject | | (Hold Reference to)
+-------------+ | RealSubject |
+-------------+
Java代码示例
以银行账户访问为例:
java
// Step 1: 创建一个接口
interface BankAccount {
void deposit(double amount);
void withdraw(double amount);
double getBalance();
}
// Step 2: 实现真实对象类
class RealBankAccount implements BankAccount {
private double balance;
public RealBankAccount(double balance) {
this.balance = balance;
}
@Override
public void deposit(double amount) {
balance += amount;
System.out.println("Deposited " + amount + ", Current Balance: " + balance);
}
@Override
public void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
System.out.println("Withdrew " + amount + ", Current Balance: " + balance);
} else {
System.out.println("Insufficient funds");
}
}
@Override
public double getBalance() {
return balance;
}
}
// Step 3: 创建代理对象类
class BankAccountProxy implements BankAccount {
private RealBankAccount realBankAccount;
private String owner;
public BankAccountProxy(String owner, double initialBalance) {
this.owner = owner;
this.realBankAccount = new RealBankAccount(initialBalance);
}
@Override
public void deposit(double amount) {
System.out.println("Proxy: Deposit access granted for " + owner);
realBankAccount.deposit(amount);
}
@Override
public void withdraw(double amount) {
System.out.println("Proxy: Withdraw access granted for " + owner);
realBankAccount.withdraw(amount);
}
@Override
public double getBalance() {
System.out.println("Proxy: Balance access granted for " + owner);
return realBankAccount.getBalance();
}
}
// Step 4: 客户端代码
public class ProxyPatternExample {
public static void main(String[] args) {
BankAccount account = new BankAccountProxy("John", 1000);
account.deposit(500);
account.withdraw(300);
System.out.println("Final Balance: " + account.getBalance());
}
}
BankAccount
:定义了账户的操作,作为抽象主题。RealBankAccount
:为真实的银行账户类,实现了具体的账户操作。BankAccountProxy
:代理类,实现了对真实银行账户的代理,在每次操作之前进行一些信息打印(可以是权限校验或者日志记录等)。- 客户端只与代理类
BankAccountProxy
交互,而代理类最终会调用真实的RealBankAccount
。
现实中的应用
1. 动态代理在 RPC(远程过程调用)中的应用
RPC 机制可以让客户端调用远程服务就像调用本地服务一样。RPC 系统通常会为每个远程服务提供一个代理对象,代理对象负责将调用请求转发到远程服务器上,然后将返回结果传回客户端。这样,客户端代码不需要关注底层的网络通信逻辑,只需像调用本地对象一样使用远程服务。
2. 虚拟代理在图片加载中的应用
在图形用户界面(GUI)开发中,加载大型图片文件可能会非常耗时。因此,可以使用虚拟代理来代替图片对象,在图片真正被加载之前显示一个占位符。当图片加载完成后,代理对象才会将请求委托给真实的图片对象。这种模式常见于网页浏览器、图片查看器中,以提高用户体验。
3. 保护代理在权限控制中的应用
在大型企业级应用中,保护代理可以用于控制对敏感数据的访问。例如,某些用户可能只能查看某些报表而不能修改数据。代理类可以在请求到达真实对象之前,进行权限检查,确保只有授权用户才能进行操作。这在企业的权限管理、系统安全等方面非常有用。
4. 缓存代理在浏览器缓存中的应用
代理模式在缓存中也是一个很典型的应用。例如,在浏览器中,当用户请求某个页面时,浏览器可能会先检查代理对象是否有缓存的页面副本,如果有就直接返回,从而减少对服务器的访问。这种方式可以提高系统性能,减少网络请求带来的延迟。
代理模式的优点和缺点
优点:
- 控制访问:代理模式可以控制对目标对象的访问,例如在保护代理中,可以实现权限管理。
- 增加功能:可以在不修改目标对象的前提下,通过代理对象增加额外的功能,例如日志、计数器、延迟加载等。
- 降低系统耦合:客户端只和代理类打交道,代理类可以屏蔽底层的复杂逻辑和实现细节。
缺点:
- 增加系统复杂性:代理模式增加了一个新的代理类,使系统更加复杂。
- 延迟请求:某些代理可能会引入请求的延迟,例如远程代理需要通过网络进行通信,而虚拟代理需要延迟真实对象的创建。