设计模式之 代理模式

代理模式是一种结构型设计模式,允许通过一个代理对象来控制对另一个对象的访问。代理对象通常会执行一些额外的操作,例如权限检查、延迟初始化、日志记录等。它在客户端和目标对象之间充当中介,能够为客户端提供更加灵活和安全的访问方式。

在代理模式中,代理对象与真实对象实现相同的接口或继承自同一个父类,这样代理对象就可以代替真实对象进行操作。

一、代理模式的类型

代理模式根据其功能和行为的不同,可以分为几种不同的类型:

  1. 静态代理(Static Proxy)

    静态代理在编译时就已经确定了代理对象和真实对象的关系。在这种模式下,代理类和真实类会实现相同的接口,客户端可以通过代理类来访问真实类。静态代理的主要缺点是代理类需要与真实类一一对应,无法动态创建。

  2. 动态代理(Dynamic Proxy)

    动态代理是在程序运行时,通过反射机制动态生成代理类,代理对象和真实对象之间的关系是在运行时确定的。Java中的java.lang.reflect.Proxy类和CGLib等技术提供了动态代理的支持。动态代理的最大优点是可以在运行时动态生成代理对象,且不需要手动创建多个代理类。

  3. 远程代理(Remote Proxy)

    远程代理通常用于网络编程场景,它代表一个远程对象,在客户端与服务器之间充当代理,处理网络通信等细节。客户端通过代理对象访问远程服务器,代理会将请求转发到远程对象并返回结果。

  4. 虚拟代理(Virtual Proxy)

    虚拟代理通常用于延迟加载的场景,它代理一个资源消耗较大的对象,只有在真正需要时才会创建真实对象。比如,图像加载、数据库查询等操作,可以在用户需要时再初始化资源,减少内存和计算的开销。

  5. 保护代理(Protection Proxy)

    保护代理主要用于权限控制的场景,它控制对目标对象的访问,提供安全检查。客户端通过代理对象访问目标对象,代理在访问前进行身份验证、权限控制等安全操作,确保目标对象只能被授权的用户访问。

  6. 缓存代理(Cache Proxy)

    缓存代理负责在内存中缓存真实对象的数据,以提高性能。它可以避免频繁的重复计算或从外部系统(如数据库)获取数据。当代理对象被请求时,如果缓存中有数据,它就直接返回缓存的数据;否则,它会请求真实对象并将结果缓存。

三、代理模式的结构

代理模式通常由以下几部分组成:

  1. Subject(主题接口):定义代理对象和真实对象都要实现的接口,通常是目标对象和代理对象都要遵循的合同。

  2. RealSubject(真实主题):目标对象,代理对象实际控制和访问的对象。真实对象包含真正的业务逻辑。

  3. Proxy(代理类):代理对象,它通过实现与真实对象相同的接口来转发客户端的请求。在代理中,可能会添加一些附加的操作(如权限检查、日志记录、延迟加载等),然后转发给真实对象。

三、代理模式的应用实例
1. 静态代理

我们用购买火车票作为例子,代理类可以在调用真实对象的方法之前添加一些附加操作。

  • 卖票接口

    java 复制代码
    public interface SellTickets {
        void sell();
    }
  • 火车站

    java 复制代码
    public class TrainStation implements SellTickets{
        @Override
        public void sell() {
            System.out.println("火车站出票");
        }
    }
  • 代售点

    java 复制代码
    public class ProxyPoint implements SellTickets{
    
        TrainStation trainStation = new TrainStation();
        @Override
        public void sell() {
            System.out.println("代售点收取服务费");
            trainStation.sell();
            System.out.println("代售点将票给顾客");
        }
    }
  • 测试类

    java 复制代码
    public class Client {
        public static void main(String[] args) {
            ProxyPoint proxyPoint = new ProxyPoint();
            proxyPoint.sell();
        }
    }
  • 输出结果

2. 动态代理

JDK动态代理

Java的动态代理通过反射机制,在运行时生成代理对象。java.lang.reflect.Proxy类允许我们为接口创建动态代理类。

我们接着上述例子进行,创建卖票接口和火车站类。

  • 代理工厂类

    java 复制代码
    public class ProxyFactory {
        private TrainStation trainStation = new TrainStation();
    
        public SellTickets getProxyObject(){
            /**
             * ClassLoader loader, 类加载器,直接通过对象获取
             * Class<?>[] interfaces, 代理类实现接口的字节码对象,直接通过对象获取
             * InvocationHandler h), 代理对象的调用处理程序
             */
            SellTickets proxyInstance = (SellTickets) Proxy.newProxyInstance(
                    trainStation.getClass().getClassLoader(),
                    trainStation.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //proxy:表示代理对象,即动态生成的代理类实例,是通过 Proxy.newProxyInstance() 方法创建的。
                            //method:表示被调用的方法,是一个 Method 对象,包含了目标方法的信息(方法名、参数等)。
                            //args:是一个 Object[] 数组,包含了调用该方法时传入的参数。
                            System.out.println("收取服务费");
                            Object object = method.invoke(trainStation, args);
                            System.out.println("售票处出票");
                            return object;
                        }
                    }
            );
    
            return proxyInstance;
        }
    }
  • 测试类

    java 复制代码
    public class Client {
        public static void main(String[] args) {
            ProxyFactory proxyFactory = new ProxyFactory();
            SellTickets sellTickets = proxyFactory.getProxyObject();
            sellTickets.sell();
        }
    }
  • 运行结果

3. 虚拟代理

虚拟代理用于延迟加载,在访问时才会创建目标对象。比如在图像加载的场景中,只有当用户需要查看图片时,图像才会被加载。

java 复制代码
interface Image {
    void display();
}

class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadImage();
    }

    private void loadImage() {
        System.out.println("Loading image: " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

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 ProxyImageExample {
    public static void main(String[] args) {
        Image image = new ProxyImage("image.jpg");
        image.display();  // 第一次调用,加载图像并显示
        image.display();  // 第二次调用,直接显示
    }
}

第一次调用display()时,代理对象才会创建真实对象,并加载图像,第二次调用则直接显示图像。

四、代理模式的优缺点
优点:
  1. 增加了对象的附加功能:通过代理对象,我们可以在不修改目标对象的情况下,为其添加额外的操作(如日志、安全检查、缓存等)。

  2. 控制访问:代理模式可以用于控制访问,尤其是在远程代理和保护代理中,可以对目标对象的访问进行限制和权限控制。

  3. 延迟加载:虚拟代理可以延迟加载目标对象,减少系统开销,尤其在某些资源消耗大的操作中表现突出。

  4. 简化客户端访问:客户端只需要通过代理访问目标对象,而不需要关心目标对象的复杂性或其创建过程。

缺点:
  1. 增加系统的复杂性:代理模式引入了代理对象,可能会使系统的结构变得更加复杂,特别是静态代理模式中需要为每个真实对象创建一个对应的代理类。

  2. 性能开销:代理模式通常会引入一些额外的处理(如方法拦截、权限验证等),可能会导致性能开销,尤其是在频繁使用代理的情况下。

  3. 不适用于所有场景:代理模式虽然灵活,但并不适用于每个场景。对于某些简单的系统来说,代理模式可能显得过于复杂,使用不当可能带来不必要的额外开销。

相关推荐
小白不太白9502 小时前
设计模式之 命令模式
设计模式·命令模式
吃汉堡吃到饱2 小时前
【创建型设计模式】单例模式
单例模式·设计模式
小白不太白9502 小时前
设计模式之 备忘录模式
服务器·设计模式·备忘录模式
zzzhpzhpzzz2 小时前
设计模式——策略模式
设计模式·策略模式
入门到跑路2 小时前
【君正T31开发记录】8.了解rtsp协议及设计模式
网络协议·设计模式
小白不太白9502 小时前
设计模式之 解释器模式
java·设计模式·解释器模式
吃汉堡吃到饱3 小时前
【创建型设计模式】工厂模式
设计模式
程序员与背包客_CoderZ4 小时前
C++设计模式——Singleton单例模式
c语言·开发语言·c++·单例模式·设计模式
菜鸟、小高4 小时前
从0开始学PHP面向对象内容之常用设计模式(适配器,桥接,装饰器)
android·设计模式·php