设计模式之代理模式(详细解析)

代理模式介绍

代理模式是一种设计模式,其目的是为其他对象提供一种代理以控制对这个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及在委托类执行消息后进行后续处理。

以下是一个简单的静态代理模式的示例,实现了一个打印接口,代理类在调用具体实现类的方法时打印出方法名以及参数。

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 直接访问 realImagedisplay()方法。这样可以避免一次性加载大量图片而影响程序性能。

需要注意的是,虚拟代理 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. 可能会导致访问真实对象的速度变慢,因为需要通过代理对象来访问真实对象。

在实际应用中,代理模式被广泛应用于网络编程、数据库操作、安全控制等领域。

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨影飞痕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值