05.静态代理设计模式

目录介绍
  • 01.静态代理模式基础

    • 1.1 静态代理由来
    • 1.2 静态代理定义
    • 1.3 静态代理场景
    • 1.4 静态代理思考
  • 02.静态代理原理与实现

    • 2.1 罗列一个场景
    • 2.2 用一个例子理解代理
    • 2.3 案例演变分析
  • 03.静态代理分析

    • 3.1 静态代理结构图
    • 3.2 静态代理时序图
  • 04.代理模式优势

    • 4.1 如何降低耦合
    • 4.2 保护真实对象使用权限
  • 05.静态代理不足

    • 5.1 静态代理类优缺点
    • 5.2 静态代理缺乏灵活
    • 5.3 静态代理复用难
    • 5.4 难以动态添加功能
    • 5.5 无法实现多态
    • 5.6 思考一下局限性
    • 5.7 动态代理弥补不足
  • 06.静态代理总结

    • 6.1 总结一下学习
    • 6.2 更多内容推荐

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网

https://yccoding.com/

01.静态代理模式基础

1.0 本博客AI摘要

《静态代理设计模式》详细介绍了静态代理的基本概念、原理与实现、应用场景及优缺点。主要内容包括静态代理的由来、定义、使用场景、实现方式、结构图与时序图,以及其在降低耦合、保护对象权限等方面的优势。同时,文章也指出了静态代理的局限性,如缺乏灵活性、难以复用、难以动态添加功能等,并介绍了动态代理如何弥补这些不足。最后,通过多个实际案例和代码示例,帮助读者更好地理解和应用静态代理模式。

1.1 静态代理由来

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。

代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。

1.2 静态代理定义

静态代理定义是什么?

代理模式(Proxy Pattern) :为了隐藏与保护目标对象,给某一个对象提供一个代理,并由代理对象控制对原对象的引用

1.3 静态代理场景

静态代理使用场景

代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。

我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在 RPC、缓存等应用场景中。

1.4 静态代理思考

思考一下:代理类和被代理类的关系?

静态代理中,代理类和被代理类之间的关系在编译时就已经确定。需要考虑代理类和被代理类之间的接口或继承关系,以确保代理类能够正确地代理被代理类的功能。

思考一下:代理类的责任?

代理类在静态代理中充当了中介的角色,负责将客户端的请求转发给被代理类,并在必要时添加额外的功能或控制。需要考虑代理类应该承担的责任,以及如何在代理类中实现这些责任。

思考一下:代理类的性能和效率?

静态代理在编译时就已经确定代理类和被代理类的关系,因此在运行时不需要进行额外的动态代理操作。这可能会带来一些性能和效率上的优势,但也需要考虑代理类的创建和销毁成本,以及代理类对系统性能的影响。

思考一下:代理类的扩展性和维护性?

静态代理在编译时就已经确定代理类和被代理类的关系,因此在需要代理多个类或接口时,就需要编写多个代理类。这可能会导致代码冗余和维护困难。需要考虑如何设计代理类,以提高代码的扩展性和维护性。

02.静态代理原理与实现

2.1 罗列一个场景

第一个类比:代理对象是中介,原对象是房东的房子,客户就是我们需要找房的人。

房东的房子让中介代理出租,中介可以直接控制房子出租,用户是不能找房东直接租房子的,而是要间接通过中介租房子。

第二个类比:代理对象是售票点,原对象是火车站,客户就是需要买票的乘客。

我们购买火车票可以去火车站买,但是也可以去火车票代售处买,此处的火车票代售处就是火车站购票的代理,即我们在代售点发出买票请求,代售点会把请求发给火车站,火车站把购买成功响应发给代售点,代售点再告诉你。

但是代售点只能买票,不能退票,而火车站能买票也能退票,因此代理对象支持的操作可能和委托对象的操作有所不同。

第三个类比:代理对象是vpn,原对象是国外网站,客户就是需要翻墙上网的人。

(1)用户把HTTP请求发给代理;(2)代理把HTTP请求发给web服务器;(3)web服务器把HTTP响应发给代理;(4)代理把HTTP响应发回给用户

2.2 用一个例子理解代理

实现模式

  1. 首先创建一个接口(代理都是面向接口的),
  2. 创建具体实现类来实现这个接口,
  3. 创建一个代理类同样实现这个接口,不同之处在于具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。

举一个实际案例

  1. 定义用户找房子的需求接口
  2. 创建用户找房子具体实现类
  3. 创建一个代理中介,委托中介去找房子

代码如下所示,可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。代码案例放到GitHub上,设计模式代码案例

/**
 * 静态代理伪代码
 */
private void testProxy() {
    //1.创建委托对象
    RealSubject subject = new RealSubject();
    //2.创建调用处理器对象
    MyProxy p = new MyProxy(subject);
    //3.通过代理对象调用方法
    p.request();
}

/**
 * 代理类和委托类会实现接口
 */
interface Subject{
    void request();
}

/**
 * 委托类
 */
class RealSubject implements Subject{
    @Override
    public void request(){
        System.out.println("request");
    }
}

/**
 * 代理
 */
class Proxy implements Subject{
    private Subject subject;
    public Proxy(Subject subject){
        this.subject = subject;
    }
    @Override
    public void request(){
        System.out.println("PreProcess");
        subject.request();
        System.out.println("PostProcess");
    }
}

2.3 案例演变分析

考虑一下这个需求:

给委托类增加一个过滤功能,只租房给我们这类逗比程序员。通过静态代理,我们无需修改委托类的代码就可以实现,只需在代理类中的方法中添加一个判断即可如下所示:

class MyProxy implements Subject {
    private Subject subject;
    public MyProxy(Subject subject){
        this.subject = subject;
    }
    @Override
    public void request(){
        //判断是否是逗比程序员
        if (isDouBi){
            System.out.println("PreProcess");
            subject.request();
            System.out.println("PostProcess");
        }
    }
}

使用代理的第二个优点:

可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类。

03.静态代理分析

3.1 静态代理结构图

代理模式包含如下角色:

  1. Subject: 抽象主题角色
  2. Proxy: 代理主题角色
  3. RealSubject: 真实主题角色

静态代理结构图

image

3.2 静态代理时序图

静态代理时序图如下所示

image

04.代理模式优势

4.1 如何降低耦合

一个简单的案例,演示了如何使用静态代理来降低耦合:

假设我们有一个邮件发送的接口 MailSender,以及一个实现该接口的具体类 RealMailSender。

现在我们想要在发送邮件之前记录日志,但又不想直接修改 RealMailSender 类。这时可以使用静态代理来实现:

// 定义邮件发送接口
interface MailSender {
    void sendMail(String recipient, String message);
}

// 实现邮件发送接口的具体类
class RealMailSender implements MailSender {
    public void sendMail(String recipient, String message) {
        System.out.println("Sending mail to " + recipient + ": " + message);
    }
}

// 静态代理类
class MailSenderProxy implements MailSender {
    private RealMailSender realMailSender;

    public MailSenderProxy() {
        this.realMailSender = new RealMailSender();
    }

    public void sendMail(String recipient, String message) {
        System.out.println("Logging mail sending...");
        realMailSender.sendMail(recipient, message);
    }
}
    
// 使用示例
public class Main {
    public static void main(String[] args) {
        MailSender mailSender = new MailSenderProxy();
        mailSender.sendMail("example@example.com", "Hello, world!");
    }
}

代码案例分析,它降低耦合的设计思想是什么

通过使用静态代理,我们可以在不修改 RealMailSender 类的情况下,为邮件发送操作添加额外的功能。这样,RealMailSender 类和 MailSenderProxy 类之间的耦合度降低,客户端只需要与 MailSender 接口进行交互,而不需要关心具体的实现类。

静态代理可以通过以下方式降低耦合:

  1. 接口抽象:定义一个接口,代理类和被代理类都实现该接口。通过接口的抽象,代理类和被代理类之间的耦合度降低,客户端只需要与接口进行交互,而不需要关心具体的实现类。
  2. 代理类作为中介:代理类作为客户端和被代理类之间的中介,将客户端的请求转发给被代理类。这样,客户端只需要与代理类进行交互,而不需要直接与被代理类进行交互,从而降低了客户端与被代理类之间的耦合。
  3. 面向接口编程:在客户端代码中,尽量使用接口类型来声明变量,而不是具体的实现类。这样可以使客户端与具体的代理类解耦,提高代码的灵活性和可扩展性。
  4. 使用依赖注入:通过依赖注入的方式,将代理对象注入到客户端中,而不是在客户端代码中直接创建代理对象。这样可以将代理对象的创建和配置与客户端代码解耦,提高代码的可维护性和可测试性。

4.2 保护真实对象使用权限

静态代理可以用于保护真实对象的使用权限。

通过代理类,可以在访问真实对象之前或之后执行一些额外的操作,例如权限验证、身份验证等,以确保只有具有适当权限的用户可以访问真实对象。

以下是一个简单的示例,演示了如何使用静态代理来保护真实对象的使用权限:

  • Image接口定义了display方法,RealImage类实现了该接口并表示真实对象。ImageProxy类也实现了Image接口,并在display方法中创建或使用真实对象。
  • 通过ImageProxy类,我们可以在访问真实对象之前进行权限验证或其他操作。只有在真正需要显示图像时,真实对象才会被加载和显示,从而保护了真实对象的使用权限。
private static void proxyImage() {
    Image image = new ImageProxy("image.jpg");
    // 只有在调用display方法时,真实对象才会被加载和显示
    image.display();
}


// 定义接口
interface Image {
    void display();
}

// 定义真实对象
static class RealImage implements Image {
    private String filename;

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

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

    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}
// 定义代理类
static class ImageProxy implements Image {
    private String filename;
    private RealImage realImage;

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

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

05.静态代理不足

5.1 静态代理类优缺点

优点:

业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。

缺点:

  1. 1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
  2. 2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

5.2 静态代理缺乏灵活

静态代理缺乏灵活说明:

静态代理在编译时就已经确定代理类和被代理类的关系,因此无法在运行时动态地改变代理行为。而动态代理允许在运行时动态地创建代理对象,并可以根据需要动态地改变代理行为,提供更大的灵活性。

5.3 静态代理复用难

代码复用难,会导致代码庞大:

静态代理需要为每个被代理类编写一个代理类,当需要代理多个类时,会导致代码冗余。而动态代理可以通过一个通用的代理类来代理多个类,实现代码的复用。

5.4 难以动态添加功能

静态代理动态添加功能:

静态代理在编译时就已经确定代理类和被代理类的关系,无法在运行时动态地添加额外的功能。而动态代理可以在运行时动态地为被代理对象添加额外的功能,例如日志记录、性能监控、事务管理等。

5.5 无法实现多态

静态代理无法满足多态性:

静态代理在编译时就已经确定代理类和被代理类的关系,无法实现多态性。而动态代理可以基于接口或基类来创建代理对象,实现多态性,使得代理对象可以替代被代理对象的使用。

5.6 思考一下局限性

如果要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其作为代理对象的内部属性。

但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

5.7 动态代理弥补不足

  1. 代码复用:动态代理可以通过一个通用的代理类来代理多个类,实现代码的复用。不需要为每个被代理类编写一个代理类,减少了代码的冗余。
  2. 灵活性和扩展性:动态代理在运行时动态地创建代理对象,并可以根据需要动态地添加、修改或删除代理行为。这使得代理行为可以根据不同的需求进行定制和扩展,提供了更大的灵活性和扩展性。
  3. 解耦合:动态代理通过使用接口或基类来代理对象,实现了代理类和被代理类之间的松耦合。客户端只需要与代理对象进行交互,无需关心具体的实现类,提高了代码的可维护性和可扩展性。

总结起来,静态代理在一些简单的场景下可以使用,但在需要更大的灵活性、扩展性和可维护性时,动态代理更为适合

06.静态代理总结

6.1 总结一下学习

01.静态代理模式基础

由来和背景:在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。比如以下场景就适合用代理模式

  1. 个人,通过中介来找房东发布的租房。
  2. 乘客,通过火车站来购买铁路局的车票。
  3. 客户,通过VPN代理来翻墙查阅国外资料。

一句话概括就是:代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用

静态代理使用场景:监控、统计、鉴权,日志等场景。附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发!

02.静态代理原理与实现

实现模式

  1. 首先创建一个接口(代理都是面向接口的),
  2. 创建具体实现类来实现这个接口,
  3. 创建一个代理类同样实现这个接口,不同之处在于具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。

举一个实际案例

  1. 定义用户找房子的需求接口
  2. 创建用户找房子具体实现类
  3. 创建一个代理中介,委托中介去找房子

03.静态代理分析

代理模式包含如下角色:

  1. Subject: 抽象主题角色
  2. Proxy: 代理主题角色
  3. RealSubject: 真实主题角色

04.代理模式优势

  1. 使用静态代理来降低耦合。比如,邮件发送的接口 MailSender,以及一个实现该接口的具体类 RealMailSender。可以在不修改 RealMailSender 类的情况下,为邮件发送操作添加额外的功能。这样,RealMailSender 类和 MailSenderProxy 类之间的耦合度降低,客户端只需要与 MailSender 接口进行交互,而不需要关心具体的实现类。
  2. 静态代理可以用于保护真实对象的使用权限。在真正需要显示图像时,真实对象才会被加载和显示,从而保护了真实对象的使用权限。

05.静态代理不足

  1. 静态代理缺乏灵活说明
  2. 代码复用难,会导致代码庞大
  3. 难以动态添加功能
  4. 静态代理无法满足多态性

6.2 更多内容推荐

模块描述备注
GitHub多个YC系列开源项目,包含Android组件库,以及多个案例GitHub
博客汇总汇聚Java,Android,C/C++,网络协议,算法,编程总结等YCBlogs
设计模式六大设计原则,23种设计模式,设计模式案例,面向对象思想设计模式
Java进阶数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVMJava高级
网络协议网络实际案例,网络原理和分层,Https,网络请求,故障排查网络协议
计算机原理计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理计算机基础
学习C编程C语言入门级别系统全面的学习教程,学习三到四个综合案例C编程
C++编程C++语言入门级别系统全面的教学教程,并发编程,核心原理C++编程
算法实践专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等Leetcode
Android基础入门,开源库解读,性能优化,Framework,方案设计Android

23种设计模式

23种设计模式 & 描述 & 核心作用包括
创建型模式
提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式
关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式
特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)

6.3 更多内容


杨充
221 声望42 粉丝