设计模式 – 代理模式
内容目录

设计模式 – 代理模式

前置知识:
"主题"是接口或抽象类的统称,定义了行为规范;
"主体"是实现这些行为的具体对象,是实际执行业务逻辑的实体。

一、概念

代理模式是一种结构型设计模式。

它通过提供一个代理类来代表或“代理”另一个类(称为真实主题)。

代理类和真实主题具有相同的接口,因此可以在不改变原有代码的基础上,对真实主题的功能进行增强、控制访问或提供额外的逻辑处理。

换句话说,就是用于为其他对象提供一个替身或占位符,以控制对这个对象的访问。

举例说明:

A是买房人(真实主题)、B是全权委托人:中介(代理)、C是卖房人(调用方)。

卖房人在让买房人上门看房时,中介可以先看然后再让买房人来看房子;卖房人与买房人签订购买合同时,也要中介先看一次再把合同交给买房人,签完后再给到中介,由中介转交给卖房人。

看房是买房人在看,真正签合同出钱的还是买房人。那么加这个中介的目的是什么呢?

  • 中介可以在买房人看房前先看并查询房屋的使用问题、质量问题、邻里关系、卖房人身份信息等,在确认没有问题后再让买房人上门看房;

  • 在签订购买协议前先看可以核实合同的有效性、合同内容等,签完合同后看买卖双方是否签错、漏签等。

  • 甚至买卖双方完全可以没见过面。看房由中介和买房人视频看,签合同也可以由中介来回转交,从而让买卖双方在未见面的情况下完成交易,从而实现解耦。

中介在不破坏卖房人和买房人原本的行为的同时,扦插进入很多新的行为,这些行为会在某个时间点被触发从而被执行,以此来加入更多的功能和作用。

二、分类

代理模式主要分为以下几类:

静态代理: 代理类在编译期间就被创建,程序员手动实现具体代理类,直接引用真实主题,并在其中添加额外逻辑。

动态代理:

  • JDK动态代理: 基于Java的反射机制,为接口生成代理类,适用于目标对象实现了接口的情况。
  • CGLIB动态代理: 通过字节码操作,为没有实现接口的目标对象生成子类代理,适合任何类。

三、使用动机和目的

动机: 在不修改原有代码的情况下,增强或控制对目标对象的访问,比如增加日志、安全控制、延迟初始化等。

目的: 提高系统的灵活性、扩展性和可维护性,同时降低模块间的耦合度。

四、核心组件与关系

  • Subject(主题接口): 定义了代理类和真实主题的公共接口。
  • RealSubject(真实主题): 实现主题接口,包含核心业务逻辑。
  • Proxy(代理): 同样实现主题接口,内部持有一个真实主题的引用,可以预处理请求、后处理结果,或控制对真实主题的访问。

五、优缺点

5.1 优点

  • 增加灵活性: 可以在不修改目标对象的情况下,添加额外功能。
  • 控制访问: 可以控制对真实主题的直接访问,增强安全性。
  • 延迟初始化: 通过代理可以实现懒加载,提高性能。
  • 扩展功能: 易于扩展系统功能,符合开闭原则。

5.2 缺点

  • 增加系统复杂度: 引入代理类,增加了类的数量和系统的复杂度。
  • 性能开销: 特别是动态代理,可能会带来一定的性能损失。

六、应用场景

  • 远程代理: 为远程服务提供本地代理对象,隐藏网络通信细节。
  • 虚拟代理: 对资源密集型对象进行延迟加载,如大图像、大数据集。
  • 保护代理: 控制对敏感对象的访问,实施权限检查。
  • 日志代理: 在方法调用前后添加日志记录。
  • 选用时机: 当需要在不修改原对象的基础上,增强其功能或控制访问时,考虑使用代理模式。

七、注意事项

  • 平衡性能与灵活性: 根据实际情况权衡静态代理和动态代理的使用。
  • 设计清晰的接口: 确保代理类和真实主题遵循相同的接口规范,保证替换的透明性。
  • 适度使用: 避免过度设计,只在确实需要增强或控制的地方使用代理。

八、代码实现

8.1 静态代理示例

这段代码演示了Java中代理模式的应用,具体是静态代理的实现方式。

代理模式主要用于在不修改原有对象的基础上,提供额外的处理逻辑,比如控制访问、增强功能等。

下面是对代码中代理模式使用的详细解释:

主题接口 (Image)

首先定义了一个接口 Image,它声明了一个方法 display()

这个接口充当了代理模式中的“主题接口”,定义了真实主题和代理需要实现的行为规范。

// 主题接口
interface Image {
    void display(); // 显示
}

真实主题 (RealImage)

RealImage 类实现了 Image 接口,代表了真正的业务逻辑对象,即“真实主题”。

在这个例子中,RealImage 负责加载并显示图片,模拟了可能较为耗时或资源密集的操作(比如从磁盘加载图片)。

// 真实主题
class RealImage implements Image {
    private String filename;

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

    // 从磁盘加载
    private void loadFromDisk(String filename) {
        // 加载图片的逻辑
        System.out.println("Loading " + filename);
    }

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

静态代理 (ImageProxy)

ImageProxy 同样实现了 Image 接口。

作为真实主题 RealImage 的代理,它的主要作用是控制对 RealImage 实例的访问,实现延迟加载逻辑,即只有在真正需要显示图片时才创建 RealImage 对象,从而提高性能,减少不必要的资源消耗。

// 静态代理
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();
    }
}

客户端代码

客户端代码通过 ImageProxy 来请求显示图片,而不需要直接与 RealImage 交互。

这样,即使多次调用 display() 方法,图片也只会被加载一次,因为代理负责管理 RealImage 的创建和复用。

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Image image = new ImageProxy("hi-res-image.jpg");
        image.display();
    }
}

总结来说,这段代码展示了代理模式如何通过引入代理类(ImageProxy)来控制对真实主题(RealImage)的访问,实现了延迟加载图片的功能,提高了程序的效率和响应速度。

代理模式的关键在于代理类与真实主题遵循相同的接口,从而对客户端透明,使其能够以一致的方式对待真实主题和代理。

8.2 动态代理示例(JDK动态代理)

InvocationHandler:调用处理程序接口

InvocationHandler 接口是JDK提供的,它是一个核心接口,用于定义代理对象在调用其方法时所执行的处理逻辑。

具体来说,实现这个接口的类需要重写 invoke 方法,这个方法会在代理对象的任何方法被调用时触发。在 invoke 方法内部,你可以定义在调用真实对象之前或之后需要执行的任何操作,比如日志记录、权限验证、性能监控等附加功能。这为代理模式提供了极大的灵活性和扩展性。

此例中,我们将创建一个简单的日志记录功能,通过动态代理来增强一个简单消息服务的功能。

消息服务接口:MessageService

// 消息服务接口
public interface MessageService {
    void sendMessage(String msg);
}

消息服务实现类:RealMessageService

// 真实主题:消息服务实现类
class RealMessageService implements MessageService {
    @Override
    public void sendMessage(String msg) {
        System.out.println("发送消息内容: " + msg);
    }
}

动态日志代理类:LoggingProxy

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 动态代理类
class LoggingProxy implements InvocationHandler {
    private Object target;

    public LoggingProxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("这是目标方法执行前日志,方法名: " + method.getName());
        Object result = method.invoke(target, args); // 调用目标方法
        System.out.println("这是目标方法执行后日志,方法名:" + method.getName());
        return result;
    }
}

客户端类:DynamicProxyDemo

// 客户端代码
public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 创建真实主题实例
        MessageService realService = new RealMessageService();

        // 创建动态代理实例
        LoggingProxy loggingProxy = new LoggingProxy(realService);
        MessageService proxyService = (MessageService) loggingProxy.getProxyInstance();

        // 通过代理调用方法
        proxyService.sendMessage("Hello, world!");
    }
}

运行结果:

这是目标方法执行前日志,方法名: sendMessage

发送消息内容: Hello, world!

这是目标方法执行后日志,方法名:sendMessage

这段代码演示了如何使用JDK动态代理来为 MessageService 接口的实现类 RealMessageService 添加日志记录功能。

LoggingProxy 类实现了 InvocationHandler 接口,它在调用真实主题的方法前后分别添加了日志记录逻辑。

main 方法中,我们创建了真实主题的实例,并通过 LoggingProxy 获取到代理对象,然后通过代理对象调用 sendMessage 方法,可以看到日志记录功能被正确执行。

版权声明:本文《设计模式 – 代理模式》是由陶其原创撰写,首发于陶其的个人博客
转载声明:如需转载本文,请务必在转载处保留原文链接:https://www.tqazy.com/?p=641,并明确注明文章来源。
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇