设计模式之 代理模式

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

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

一、代理模式的类型

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

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

相关推荐
Java致死3 小时前
设计模式Java
java·开发语言·设计模式
ghost14315 小时前
C#学习第23天:面向对象设计模式
开发语言·学习·设计模式·c#
敲代码的 蜡笔小新19 小时前
【行为型之迭代器模式】游戏开发实战——Unity高效集合遍历与场景管理的架构精髓
unity·设计模式·c#·迭代器模式
敲代码的 蜡笔小新2 天前
【行为型之命令模式】游戏开发实战——Unity可撤销系统与高级输入管理的架构秘钥
unity·设计模式·架构·命令模式
m0_555762902 天前
D-Pointer(Pimpl)设计模式(指向实现的指针)
设计模式
小Mie不吃饭2 天前
【23种设计模式】分类结构有哪些?
java·设计模式·设计规范
君鼎2 天前
C++设计模式——单例模式
c++·单例模式·设计模式
敲代码的 蜡笔小新2 天前
【行为型之中介者模式】游戏开发实战——Unity复杂系统协调与通信架构的核心秘诀
unity·设计模式·c#·中介者模式
令狐前生2 天前
设计模式学习整理
学习·设计模式
敲代码的 蜡笔小新2 天前
【行为型之解释器模式】游戏开发实战——Unity动态公式解析与脚本系统的架构奥秘
unity·设计模式·游戏引擎·解释器模式