代理模式介绍
代理模式
是一种设计模式,其目的是为其他对象提供一种代理以控制对这个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及在委托类执行消息后进行后续处理。
以下是一个简单的静态代理模式的示例,实现了一个打印接口,代理类在调用具体实现类的方法时打印出方法名以及参数。
interface Printer {
void print(String str);
}
class RealPrinter implements Printer {
@Override
public void print(String str) {
System.out.println("Printing: " + str);
}
}
class PrinterProxy implements Printer {
private final RealPrinter realPrinter;
public PrinterProxy(final RealPrinter realPrinter) {
this.realPrinter = realPrinter;
}
@Override
public void print(String str) {
System.out.println("Calling method 'print' with parameter: " + str);
this.realPrinter.print(str);
}
}
public class StaticProxyDemo {
public static void main(final String[] args) {
final RealPrinter realPrinter = new RealPrinter();
final PrinterProxy printerProxy = new PrinterProxy(realPrinter);
printerProxy.print("Hello, world!");
}
}
输出结果:
Calling method 'print' with parameter: Hello, world!
Printing: Hello, world!
代理模式的应用场景
代理模式是一种常见的设计模式,通常在以下应用场景中使用:
1. 远程代理
客户端
通过代理来访问远程对象。这通常使用在网络编程中,例如远程RPC调用
(在这种情况下,客户端通过代理来访问远程对象,代理将请求传递给远程对象并返回执行结果)。
下面是实现远程RPC调用的步骤
1. 定义远程接口:定义远程接口,这个接口规定了客户端可以调用的方法及其参数和返回值。
2. 实现远程接口:实现远程接口的具体功能,这个是要运行在服务器端的。
3. 实现代理类:代理类中包含一个远程接口对象,客户端通过代理访问远程对象时,代理将请求转发给远程接口对象,并将返回值传递给客户端。
4. 在客户端使用代理:客户端通过代理来访问远程对象,调用代理的方法即可实现 RPC 调用。
下面是一个简单的例子,实现一个远程计算器服务:
1. 远程接口 RemoteCalculator.java
public interface RemoteCalculator {
int add(int a, int b) throws RemoteException;
}
2. 实现远程接口 CalculatorImpl.java
public class CalculatorImpl implements RemoteCalculator {
public int add(int a, int b) {
return a + b;
}
}
3. 实现代理类 RemoteCalculatorProxy.java
public class RemoteCalculatorProxy implements RemoteCalculator {
private CalculatorImpl remoteCalculator;
public RemoteCalculatorProxy(CalculatorImpl remoteCalculator) {
this.remoteCalculator = remoteCalculator;
}
public int add(int a, int b) throws RemoteException {
// 将请求转发给远程对象
int result = remoteCalculator.add(a, b);
// 返回执行结果
return result;
}
}
4. 在客户端使用代理进行 RPC 调用
public class Client {
public static void main(String[] args) throws Exception {
// 连接到远程计算器服务
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
RemoteCalculator calculator = (RemoteCalculator) registry.lookup("CalculatorService");
// 使用代理进行 RPC 调用
RemoteCalculatorProxy proxy = new RemoteCalculatorProxy((CalculatorImpl) calculator);
int result = proxy.add(1, 2);
System.out.println("Result: " + result);
}
}
其中,需要注意的是
,远程接口中的方法必须声明抛出 RemoteException 异常
,代理类中需要处理这个异常并返回给客户端。在客户端使用代理进行 RPC 调用时,需要首先
连接到远程计算器服务,这可以使用 RMI 的 LocateRegistry 类
实现。在这个例子中,服务是运行在本地的,因此连接到 localhost
。
使用代理进行 RPC 调用时,需要将 CalculatorImpl 对象传递给 RemoteCalculatorProxy 构造函数。
2. 虚拟代理
在创建对象时,代理对象作为对象的占位符进行了初始化,从而提供了更高效的访问
。这通常使用在大对象的懒加载中。
代理模式可以实现虚拟代理。虚拟代理在客户端访问真实对象之前,先访问一个代理对象,代理对象将真实对象的创建延迟到了客户端第一次访问它时。虚拟代理可以有效地提高程序的性能和响应速度
。
下面是一个简单的例子,实现一个图片浏览器的虚拟代理
1. 定义图片接口 Image.java
public interface Image {
void display();
}
2. 实现真实图片类 RealImage.java
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
3. 实现虚拟代理类 ProxyImage.java
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
4. 在客户端使用虚拟代理
public class Client {
public static void main(String[] args) {
Image image1 = new ProxyImage("test1.jpg");
Image image2 = new ProxyImage("test2.jpg");
image1.display();
image2.display();
}
}
在上面的例子中,虚拟代理ProxyImage
在客户端第一次访问图片时创建了真实图片 RealImage
。在之后的访问中,ProxyImage
直接访问 realImage
的display()
方法。这样可以避免一次性加载大量图片而影响程序性能。
需要注意的是,虚拟代理 ProxyImage 和真实图片 RealImage 都实现了图片接口 Image,因此客户端可以使用相同的方式来访问它们。
3. 安全代理
用于控制对对象的访问
,例如在授权之前需要进行身份验证。
代理模式可以实现安全代理。安全代理在客户端访问真实对象之前,先访问一个代理对象进行权限验证,如果验证通过,则允许客户端访问真实对象,否则拒绝客户端访问。安全代理通常用于需要进行权限控制的场景,如操作系统中的用户权限控制
等。
下面是一个简单的例子,实现一个文件读取器的安全代理,限制客户端只能读取指定目录下的文件
1. 定义文件读取接口 FileReader.java
public interface FileReader {
void readFile(String fileName);
}
2. 实现真实文件读取类 RealFileReader.java
public class RealFileReader implements FileReader {
public void readFile(String fileName) {
System.out.println("Reading file " + fileName);
}
}
3. 实现安全代理类 SecureFileReader.java
public class SecureFileReader implements FileReader {
private RealFileReader realFileReader;
public void readFile(String fileName) {
if (isReadAllowed(fileName)) {
if (realFileReader == null) {
realFileReader = new RealFileReader();
}
realFileReader.readFile(fileName);
} else {
System.out.println("Access denied for file " + fileName);
}
}
private boolean isReadAllowed(String fileName) {
// 简单的权限控制逻辑:只允许读取指定目录下的文件
return fileName.startsWith("/home/user/files/");
}
}
4. 在客户端使用安全代理
public class Client {
public static void main(String[] args) {
FileReader fileReader = new SecureFileReader();
fileReader.readFile("/home/user/files/test.txt"); // 允许读取
fileReader.readFile("/etc/passwd"); // 拒绝访问
}
}
在上面的例子中,安全代理 SecureFileReader
在客户端访问真实文件读取器 RealFileReader
之前,先进行了权限验证。如果客户端请求访问的文件在指定目录下,则允许客户端访问真实文件读取器,否则拒绝客户端访问。这样可以避免客户端读取不合法的文件。
4. 智能代理
在访问对象时,代理可以执行额外的操作
,例如计算对象的引用次数,或者在使用对象之前进行对象的初始化。
代理模式可以实现智能代理,智能代理可以在客户端访问真实对象之前进行一些预处理
,如缓存真实对象的结果,以提高性能,或者在真实对象不可用时,返回默认值等。
下面是一个简单的例子,实现一个下载器的智能代理,实现文件下载功能,并在需要时缓存下载结果
1. 定义文件下载接口 Downloader.java
public interface Downloader {
void download(String fileUrl);
}
2. 实现真实文件下载类 RealDownloader.java
public class RealDownloader implements Downloader {
public void download(String fileUrl) {
// 模拟下载文件,并输出下载进度
System.out.print("Downloading " + fileUrl + ": ");
for (int i = 0; i < 100; i += 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(i + "% ");
}
System.out.println("downloaded.");
}
}
3. 实现智能代理类 SmartDownloader.java
public class SmartDownloader implements Downloader {
private RealDownloader realDownloader;
private Map<String, String> cache; // 缓存已下载的文件
public SmartDownloader() {
cache = new HashMap<>();
}
public void download(String fileUrl) {
if (cache.containsKey(fileUrl)) { // 如果文件已经下载过了,则直接返回缓存结果
System.out.println("File " + fileUrl + " found in cache, return cached result.");
System.out.println(cache.get(fileUrl));
} else {
if (realDownloader == null) {
realDownloader = new RealDownloader();
}
realDownloader.download(fileUrl);
cache.put(fileUrl, "File " + fileUrl + " downloaded."); // 缓存下载结果
}
}
}
4. 在客户端使用智能代理
public class Client {
public static void main(String[] args) {
Downloader downloader = new SmartDownloader();
downloader.download("http://example.com/test.txt"); // 第一次下载
downloader.download("http://example.com/test.txt"); // 第二次下载,从缓存中获取
downloader.download("http://example.com/image.jpg"); // 第一次下载另一个文件
downloader.download("http://example.com/image.jpg"); // 第二次下载另一个文件,从缓存中获取
}
}
在上面的例子中,智能代理 SmartDownloader
在客户端访问真实文件下载器 RealDownloader
之前,先进行了预处理
。
如果文件已经下载过了,则直接返回缓存结果;否则下载文件,并将下载结果存入缓存中。这样可以避免重复下载或频繁下载相同文件,提高下载效率
。
5. 日志记录代理
用于记录对象的访问日志,以便进行调试和错误跟踪
。
代理模式可以实现日志记录代理,日志记录代理可以在客户端访问真实对象时记录日志
,以便于后续的分析和调试。
下面是一个简单的例子,实现一个打印机的日志记录代理,记录打印机的打印操作及结果
1. 定义打印机接口 Printer.java
public interface Printer {
void print(String content);
}
2. 实现真实打印机类 RealPrinter.java
public class RealPrinter implements Printer {
public void print(String content) {
System.out.println("[RealPrinter] Printing: " + content);
}
}
3. 实现日志记录代理类 LoggingPrinter.java
public class LoggingPrinter implements Printer {
private RealPrinter realPrinter;
public LoggingPrinter(RealPrinter realPrinter) {
this.realPrinter = realPrinter;
}
public void print(String content) {
System.out.println("[LoggingPrinter] Printing: " + content);
realPrinter.print(content);
System.out.println("[LoggingPrinter] Print finished.");
}
}
4. 在客户端使用日志记录代理
public class Client {
public static void main(String[] args) {
RealPrinter realPrinter = new RealPrinter();
LoggingPrinter loggingPrinter = new LoggingPrinter(realPrinter);
loggingPrinter.print("Hello, world!");
}
}
在上面的例子中,日志记录代理 LoggingPrinter
在客户端访问真实打印机 RealPrinter
时,先记录打印操作及打印内容,然后调用真实打印机的打印方法,并记录打印结束时间,以便于后续的分析和调试。
6. 委托代理
将任务委托给代理来执行,从而实现对任务的控制和管理
。
代理模式可以实现委托代理,即代理对象将请求委托给其它对象来处理。
下面是一个简单的例子,实现一个文件读取器的委托代理,将文件读取请求委托给真实文件读取器和缓存文件读取器来处理
1. 定义文件读取器接口 FileReader.java
public interface FileReader {
String read(String filename);
}
2. 实现真实文件读取器类 RealFileReader.java
public class RealFileReader implements FileReader {
public String read(String filename) {
System.out.println("[RealFileReader] Reading file: " + filename);
return "Content of file " + filename;
}
}
3. 实现缓存文件读取器类 CachedFileReader.java
public class CachedFileReader implements FileReader {
private RealFileReader realFileReader;
private Map<String, String> cache = new HashMap<>();
public CachedFileReader(RealFileReader realFileReader) {
this.realFileReader = realFileReader;
}
public String read(String filename) {
System.out.println("[CachedFileReader] Reading file: " + filename);
if (cache.containsKey(filename)) {
System.out.println("[CachedFileReader] File content found in cache.");
return cache.get(filename);
} else {
String content = realFileReader.read(filename);
cache.put(filename, content);
System.out.println("[CachedFileReader] File content added to cache.");
return content;
}
}
}
4. 实现委托文件读取器类 DelegatingFileReader.java
public class DelegatingFileReader implements FileReader {
private RealFileReader realFileReader = new RealFileReader();
private CachedFileReader cachedFileReader = new CachedFileReader(realFileReader);
public String read(String filename) {
if (filename.endsWith(".txt")) {
return cachedFileReader.read(filename);
} else {
return realFileReader.read(filename);
}
}
}
5. 在客户端使用委托文件读取器
public class Client {
public static void main(String[] args) {
FileReader fileReader = new DelegatingFileReader();
System.out.println(fileReader.read("file1.txt"));
System.out.println(fileReader.read("file2.png"));
System.out.println(fileReader.read("file1.txt"));
}
}
在上面的例子中,委托文件读取器 DelegatingFileReader
将文件读取请求委托给真实文件读取器RealFileReader
和缓存文件读取器 CachedFileReader
来处理。
当文件名以 “.txt” 结尾时,委托文件读取器使用缓存文件读取器来处理请求,否则使用真实文件读取器来处理请求。缓存文件读取器将文件内容缓存起来,避免重复读取同一文件,提高了文件读取的效率
。
代理模式的特点以及优缺点
代理模式是一种结构型设计模式,其主要作用是为其他对象提供一种代理以控制对其的访问。使用代理模式,可以在不改变原有对象的情况下增强或扩展其行为
。
代理模式具有以下特点
1. 代理对象和原对象实现了相同的接口,使得客户端可以统一地访问它们。
2. 代理对象可以在不改变原有对象的情况下,对其进行增强或扩展,例如实现缓存、延迟加载、安全检查等。
3. 代理模式还可以实现远程代理,让客户端可以访问位于远程的对象。
代理模式包含以下角色
1. 抽象对象角色(Subject):定义了对象的接口,与代理和真实对象具有相同的接口。
2. 真实对象角色(RealSubject):实现了抽象对象角色所定义的接口,是代理模式中真正的对象。
3. 代理对象角色(Proxy):持有真实对象的引用,并实现了与真实对象相同的接口,在需要的时候调用真实对象的方法。
代理模式的优点包括
1. 可以控制对真实对象的访问,从而实现对真实对象的保护。
2. 可以提高真实对象的性能,例如实现缓存等。
3. 可以实现远程代理,让客户端可以访问位于远程的对象。
代理模式的缺点包括
1. 会增加系统的复杂度,增加代码量。
2. 可能会导致访问真实对象的速度变慢,因为需要通过代理对象来访问真实对象。
在实际应用中,代理模式被广泛应用于网络编程、数据库操作、安全控制等领域。