代理模式


目录

  1. 代理模式简介

  2. 代理模式的意图

  3. 代理模式的结构

  4. 代理模式的实现

  5. 代理模式的适用场景

  6. 代理模式的优缺点

  7. 代理模式的实际应用实例

  8. 代理模式与其他模式的比较

  9. 总结

  10. 参考资料


1. 代理模式简介

代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。通过代理对象,客户端可以在不直接访问目标对象的情况下,间接地执行目标对象的方法。代理模式在实际应用中常用于控制对象的访问、延迟对象的创建、记录对象的访问日志等。

关键点:

  • 代理对象:代表目标对象,控制对目标对象的访问。

  • 目标对象:被代理的真实对象,实际执行业务逻辑。

  • 透明性:代理对象与目标对象实现相同的接口,客户端无需感知代理的存在。


2. 代理模式的意图

代理模式的主要目的是:

  • 控制对目标对象的访问:通过代理对象,可以在访问目标对象前后执行额外的操作,如权限检查、日志记录等。

  • 延迟对象的创建:在目标对象创建成本较高时,可以通过代理对象实现按需创建,优化资源使用。

  • 隐藏目标对象的实现细节:客户端无需了解目标对象的具体实现,只需通过代理接口进行交互。

  • 增强目标对象的功能:通过代理对象可以为目标对象添加新的功能,而无需修改目标对象本身。


3. 代理模式的结构

3.1. 结构组成

代理模式主要由以下四个角色组成:

  1. Subject(主题接口):定义了目标对象和代理对象的共同接口,确保代理对象可以替代目标对象。

  2. RealSubject(真实主题):实现了主题接口,是真正执行业务逻辑的对象。

  3. Proxy(代理对象):实现了主题接口,持有真实主题对象的引用,并控制对真实主题的访问。

  4. Client(客户端):通过主题接口与代理对象交互,无需直接访问真实主题对象。

角色关系:

  • Proxy 持有一个 RealSubject 对象的引用。

  • ProxyRealSubject 都实现了 Subject 接口。

  • Client 通过 Subject 接口与 Proxy 交互。

3.2. UML类图

以下是代理模式的简化UML类图:

    +----------------+         +----------------+
    |    Client      |         |    Subject     |
    +----------------+         +----------------+
    |                |         | + request()    |
    |                |         +----------------+
    |                |                ^
    |                |                |
    |                |         +----------------+
    |                |         |   RealSubject  |
    |                |         +----------------+
    |                |         | + request()    |
    |                |         +----------------+
    |                |
    |                |         +----------------+
    |                |         |     Proxy      |
    |                |         +----------------+
    |                |         | - realSubject  |
    |                |         +----------------+
    |                |         | + request()    |
    |                |         +----------------+
    +----------------+

说明:

  • Client 调用 Subject 接口的 request() 方法。

  • Proxy 实现了 Subject 接口,并持有 RealSubject 对象的引用。

  • Proxyrequest() 方法中,可以在调用 RealSubjectrequest() 方法之前或之后执行额外的操作。


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. 代理模式的适用场景

代理模式适用于以下场景:

  1. 需要控制对某个对象的访问:如在访问敏感资源时进行权限检查。

  2. 需要延迟对象的创建和初始化:如在需要时才创建大型对象,节省系统资源。

  3. 需要在对象访问前后执行额外的操作:如日志记录、缓存、性能监控等。

  4. 需要提供一个简化的接口:如为复杂子系统提供一个简单的访问接口。

  5. 需要远程访问对象:如在分布式系统中,通过代理对象实现远程方法调用。

示例应用场景:

  • 虚拟代理:延迟创建和加载对象,如图片加载、虚拟列表等。

  • 保护代理:控制对对象的访问权限,如安全控制、认证等。

  • 远程代理:在不同地址空间中代表对象,实现远程调用。

  • 智能引用代理:在访问对象时添加额外的操作,如计数、记录日志等。


6. 代理模式的优缺点

6.1. 优点

  1. 职责清晰:代理对象和目标对象各自承担不同的职责,代理负责控制访问,目标负责业务逻辑。

  2. 提高灵活性和可扩展性:通过代理对象可以在不修改目标对象的情况下,动态地为目标对象添加新功能。

  3. 隐藏目标对象的实现细节:客户端通过代理接口与目标对象交互,无需了解目标对象的具体实现。

  4. 节省资源:通过延迟加载或控制访问,可以节省系统资源,提高性能。

6.2. 缺点

  1. 增加系统复杂性:引入代理类会增加系统的类数量,可能使系统结构复杂。

  2. 可能影响性能:代理对象在调用目标对象的方法时会引入额外的层次,可能影响系统的性能,尤其是在代理层数较多时。

  3. 代理对象的维护:需要维护代理类和目标类的一致性,确保代理类正确地转发请求。

  4. 过度使用代理:如果不合理地使用代理模式,可能导致系统设计臃肿,难以维护。


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) 通过为其他对象提供一个代理对象,以控制对目标对象的访问,实现了对目标对象的间接访问和管理。代理模式适用于需要控制对象访问、延迟对象创建、记录访问日志等场景,特别是在系统需要对目标对象的访问进行额外控制或优化时。

关键学习点回顾:

  1. 理解代理模式的核心概念:通过代理对象控制对目标对象的访问,实现间接访问和管理。

  2. 掌握代理模式的结构:包括Subject、RealSubject、Proxy和Client之间的关系。

  3. 识别适用的应用场景:控制对象访问、延迟对象创建、记录日志、远程访问等。

  4. 认识代理模式的优缺点:职责清晰、提高灵活性和可扩展性,但增加系统复杂性、可能影响性能。

  5. 实际应用中的代理模式实例:虚拟代理、保护代理、远程代理、智能引用代理等。


10. 参考资料