Bootstrap

【软件设计】常用设计模式--代理模式

代理模式(Proxy Pattern)

1. 概念

代理模式是一种结构型设计模式,它为另一个对象提供了一个替身或占位符,以控制对该对象的访问。代理可以代替原对象执行操作、控制访问权限、延迟加载等。这种模式的关键在于代理对象和被代理对象实现相同的接口,以确保它们可以互换。

2. 模式结构

代理模式的核心角色包括:

  • 主题(Subject):定义了代理对象和真实对象的公共接口。
  • 真实主题(RealSubject):实际的业务逻辑类,代理对象通过控制对它的访问来扩展功能。
  • 代理(Proxy):持有对 RealSubject 的引用,并可以在调用前后对其操作进行控制或扩展。

3. UML 类图

Subject
+Request()
Proxy
+ RealSubject: Subject
+Request()
RealSubject
+Request()

4.实现方式

C# 示例

步骤1:定义主题接口
public interface ISubject
{
    void Request();
}
步骤2:实现真实主题
public class RealSubject : ISubject
{
    public void Request()
    {
        Console.WriteLine("RealSubject: Handling Request.");
    }
}
步骤3:实现代理类
public class Proxy : ISubject
{
    private RealSubject _realSubject;

    public void Request()
    {
        if (_realSubject == null)
        {
            _realSubject = new RealSubject();
        }
        Console.WriteLine("Proxy: Controlling access before forwarding the request.");
        _realSubject.Request();
    }
}
步骤4:客户端使用代理模式
class Program
{
    static void Main(string[] args)
    {
        ISubject proxy = new Proxy();
        proxy.Request();
    }
}
输出结果:
Proxy: Controlling access before forwarding the request.
RealSubject: Handling Request.

Java 示例

步骤1:定义主题接口
public interface Subject {
    void request();
}
步骤2:实现真实主题
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}
步骤3:实现代理类
public class Proxy implements Subject {
    private RealSubject realSubject;

    @Override
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        System.out.println("Proxy: Controlling access before forwarding the request.");
        realSubject.request();
    }
}
步骤4:客户端使用代理模式
public class Main {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.request();
    }
}
输出结果:
Proxy: Controlling access before forwarding the request.
RealSubject: Handling request.

5. 代理模式的类型

5.1 虚拟代理

虚拟代理用于控制资源密集型对象的实例化,常用于延迟加载(Lazy Initialization)场景。代理类在实际使用时才实例化真实对象,从而节省系统资源。

  • 示例:当一个图像很大且加载时间较长时,使用虚拟代理可以延迟图像的加载,只有当图像真正需要显示时才进行加载。
public class ImageProxy : IImage
{
    private RealImage _realImage;
    private string _fileName;

    public ImageProxy(string fileName)
    {
        _fileName = fileName;
    }

    public void Display()
    {
        if (_realImage == null)
        {
            _realImage = new RealImage(_fileName);
        }
        _realImage.Display();
    }
}

5.2 远程代理

远程代理为位于不同地址空间的对象(如通过网络通信的对象)提供代理。客户端通过代理访问远程服务器上的对象,而不直接处理复杂的通信逻辑。

  • 示例:当客户端需要访问远程的Web服务或数据库时,使用远程代理可以将网络通信的复杂性隐藏在代理类中。
实现步骤
步骤1:定义主题接口

与真实对象和代理对象共享相同的接口,以便客户端可以通过代理访问。

public interface Subject {
    void request();
}
步骤2:实现真实主题

真实对象实现了主题接口,并包含了实际的业务逻辑。

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}
步骤3:实现远程代理

远程代理通常会处理网络通信的细节,比如使用HTTP、Socket等。它会将请求发送到远程服务器,接收响应并返回给客户端。

import java.io.*;
import java.net.*;

public class RemoteProxy implements Subject {
    private String serverAddress;

    public RemoteProxy(String serverAddress) {
        this.serverAddress = serverAddress;
    }

    @Override
    public void request() {
        try {
            Socket socket = new Socket(serverAddress, 8080);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            out.println("Request from Remote Proxy");
            String response = in.readLine();
            System.out.println("Response from Real Subject: " + response);

            in.close();
            out.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
步骤4:客户端使用远程代理

客户端通过远程代理调用真实主题的功能。

public class Client {
    public static void main(String[] args) {
        Subject proxy = new RemoteProxy("localhost");
        proxy.request();
    }
}

服务器端示例
服务器端接收请求并返回响应:

import java.io.*;
import java.net.*;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("Server is running...");

        while (true) {
            Socket clientSocket = serverSocket.accept();
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            String request = in.readLine();
            System.out.println("Received: " + request);
            out.println("Handled by Real Subject");

            in.close();
            out.close();
            clientSocket.close();
        }
    }
}

5.3 保护代理

保护代理用于控制对原始对象的访问,主要是限制某些客户端的权限。代理会根据访问者的权限决定是否可以访问真实对象。

  • 示例:在访问控制系统中,代理可以根据用户的角色决定是否授予对特定资源的访问权。
public class ProtectedProxy : ISubject
{
    private RealSubject _realSubject;
    private string _userRole;

    public ProtectedProxy(string userRole)
    {
        _userRole = userRole;
    }

    public void Request()
    {
        if (_userRole == "Admin")
        {
            _realSubject = new RealSubject();
            _realSubject.Request();
        }
        else
        {
            Console.WriteLine("Access Denied: You don't have permission to perform this operation.");
        }
    }
}

5.4 智能代理

智能代理可以在真实对象操作的前后执行一些附加操作,例如记录日志、统计调用次数、缓存结果等。

  • 示例:在Web应用程序中,智能代理可以用于记录每个请求的处理时间。
public class LoggingProxy : ISubject
{
    private RealSubject _realSubject;

    public void Request()
    {
        Console.WriteLine("Logging: Before executing the request.");
        if (_realSubject == null)
        {
            _realSubject = new RealSubject();
        }
        _realSubject.Request();
        Console.WriteLine("Logging: After executing the request.");
    }
}
实现步骤
步骤1:定义主题接口

与之前相同,定义一个公共接口。


public interface Subject {
    void request();
}
步骤2:实现真实主题

实现主题接口的真实对象。

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}
步骤3:实现智能代理

智能代理在调用真实对象的请求方法前后执行一些附加操作,比如记录日志或计算执行时间。

public class SmartProxy implements Subject {
    private RealSubject realSubject;

    public SmartProxy() {
        this.realSubject = new RealSubject();
    }

    @Override
    public void request() {
        System.out.println("SmartProxy: Logging before request.");
        long startTime = System.currentTimeMillis();
        
        realSubject.request();
        
        long endTime = System.currentTimeMillis();
        System.out.println("SmartProxy: Logging after request. Execution time: " + (endTime - startTime) + " ms");
    }
}
步骤4:客户端使用智能代理

客户端通过智能代理调用真实主题。

public class Client {
    public static void main(String[] args) {
        Subject proxy = new SmartProxy();
        proxy.request();
    }
}

5.5 应用场景

远程代理
场景:在分布式系统中,客户端需要访问远程服务,远程代理负责处理网络通信和请求转发。
应用:RPC(远程过程调用)、RESTful API等场景。
智能代理
场景:需要对方法调用进行监控、统计或其他增强功能,智能代理可以提供附加的处理逻辑。
应用:日志记录、性能监控、缓存管理等场景。

6. 优点

  • 控制对象访问:代理模式可以控制对真实对象的访问,添加权限控制、延迟加载、网络通信等功能。
  • 节省系统资源:虚拟代理可以在对象真正需要时才创建,从而节省资源。
  • 增强功能:代理可以在真实对象执行操作前后添加额外的功能,如日志记录、缓存等。

7. 缺点

  • 增加复杂性:由于引入了代理类,系统变得更加复杂,增加了类的数量。
  • 性能开销:代理可能导致额外的开销,特别是在处理远程调用或过度使用智能代理时。

8. 代理模式应用场景

  • 远程代理:当需要访问远程对象时,可以使用远程代理隐藏通信的细节。
  • 虚拟代理:当需要延迟加载资源密集型对象时,可以使用虚拟代理。
  • 访问控制:当需要控制对某些资源或对象的访问权限时,保护代理是一个理想的选择。
  • 性能优化:使用智能代理可以在不改变原有业务逻辑的情况下,优化性能或增加功能。

9.代理模式变体

9.1 虚拟代理(Virtual Proxy)

虚拟代理用于延迟加载资源密集型对象的实例化,直到需要时才创建真实对象。这样可以节省系统资源,避免不必要的开销。

实现步骤
步骤1:定义主题接口
public interface Image {
    void display();
}
步骤2:实现真实主题
public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadImageFromDisk();
    }

    private void loadImageFromDisk() {
        System.out.println("Loading " + fileName);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }
}
步骤3:实现虚拟代理
public class ImageProxy implements Image {
    private RealImage realImage;
    private String fileName;

    public ImageProxy(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName);
        }
        realImage.display();
    }
}
步骤4:客户端使用虚拟代理
public class Client {
    public static void main(String[] args) {
        Image image = new ImageProxy("test_image.jpg");
        image.display(); // 只在此处加载
        image.display(); // 直接显示,不再加载
    }
}
应用场景
  • 适用于需要加载大型对象的场景,例如图像、视频等。
  • 在图形界面应用中,通常使用虚拟代理来延迟加载图形组件。

9.2 保护代理(Protection Proxy)

保护代理控制对真实对象的访问,主要用于权限管理。它根据客户端的身份或角色决定是否允许访问真实对象。

实现步骤
步骤1:定义主题接口
public interface Document {
    void view();
}
步骤2:实现真实主题
public class RealDocument implements Document {
    @Override
    public void view() {
        System.out.println("Viewing Document");
    }
}
步骤3:实现保护代理
public class ProtectionProxy implements Document {
    private RealDocument realDocument;
    private String userRole;

    public ProtectionProxy(String userRole) {
        this.userRole = userRole;
    }

    @Override
    public void view() {
        if (userRole.equals("Admin")) {
            if (realDocument == null) {
                realDocument = new RealDocument();
            }
            realDocument.view();
        } else {
            System.out.println("Access Denied: You do not have permission to view this document.");
        }
    }
}
步骤4:客户端使用保护代理
public class Client {
    public static void main(String[] args) {
        Document doc = new ProtectionProxy("User");
        doc.view(); // Access Denied

        Document adminDoc = new ProtectionProxy("Admin");
        adminDoc.view(); // Access Granted
    }
}
应用场景
  • 适用于敏感数据或操作,需要根据用户权限控制访问的场景。
  • 在企业级应用中常见,用于控制对重要文档或资源的访问。

9.3 缓存代理(Caching Proxy)

缓存代理在调用真实对象的方法前检查是否已经缓存了结果。如果有,则直接返回缓存结果,否则调用真实对象并将结果存入缓存。

实现步骤
步骤1:定义主题接口
public interface Data {
    String fetchData();
}
步骤2:实现真实主题
public class RealData implements Data {
    @Override
    public String fetchData() {
        return "Data from Real Data Source";
    }
}
步骤3:实现缓存代理
import java.util.HashMap;

public class CachingProxy implements Data {
    private RealData realData;
    private HashMap<String, String> cache;

    public CachingProxy() {
        this.realData = new RealData();
        this.cache = new HashMap<>();
    }

    @Override
    public String fetchData() {
        if (cache.containsKey("data")) {
            System.out.println("Returning cached data.");
            return cache.get("data");
        }
        String data = realData.fetchData();
        cache.put("data", data);
        return data;
    }
}
步骤4:客户端使用缓存代理
public class Client {
    public static void main(String[] args) {
        Data dataProxy = new CachingProxy();
        System.out.println(dataProxy.fetchData()); // First call fetches data
        System.out.println(dataProxy.fetchData()); // Subsequent call returns cached data
    }
}
应用场景
  • 适用于数据查询频繁但变化不大的场景,例如Web应用中的数据库查询结果。
  • 可以显著提高性能,减少对真实数据源的调用次数。

10.总结

代理模式在控制对象访问和增强系统功能方面提供了很大的灵活性,且每种变体都有其独特的用处,需要根据需求进行选择和实施。

;