鸣谢:《大话设计模式》

简介

是一种结构型设计模式,它允许一个对象(代理)充当另一个对象(真实对象)的接口,以控制对该对象的访问。代理通常用于在不改变客户端代码的情况下,对访问对象的行为进行控制,以实现懒加载、访问控制、监控、缓存等功能。

主要组成部分:

  • 主题接口(Subject Interface):定义了真实对象和代理对象的共同接口,客户端可以通过该接口访问真实对象。

  • 真实对象(Real Subject):实现了主题接口,代表了真正需要被代理的对象,它执行实际的操作。

  • 代理对象(Proxy):实现了主题接口,包含一个对真实对象的引用。代理对象可以控制客户端对真实对象的访问,执行一些附加操作(例如权限检查、懒加载、缓存等)。

工作流程

  • 客户端代码通过代理对象访问真实对象。

  • 代理对象可以在访问真实对象之前或之后执行一些额外的操作,以实现特定的需求。

  • 代理对象将客户端请求传递给真实对象,执行实际的操作。

  • 真实对象将结果返回给代理对象,代理对象将结果返回给客户端。

例子

书中的追求女孩的例子不太恰当,对代理模式的演义差点意思,本博客采用另一个例子,完整 例子可以在我的github中看。

结构

文件下载例子代码【推荐】

主题接口(Subject Interface)

interface FileDownloader {
    void download(String url);
}

真实对象(Real Subject)

/**
 * @author: lpy
 * @Date: 2023/10/17
 */
public class RealFileDownloader implements FileDownloader{

    @Override
    public void download(String url) {
        System.out.println("开始下载"+url);
    }
}

代理类

/**
 * @author: lpy
 * @Date: 2023/10/17
 */
public class DownloadProxy implements FileDownloader{
    private FileDownloader fl;
    private String name;

    public DownloadProxy(String username){
        this.fl = new RealFileDownloader();
        this.name = username;
    }

    @Override
    public void download(String url) {
        // 在执行前做一些操作
        checkPermission(name);
        this.logDownload(url);

        this.fl.download(url);
        // 在执行后做一些操作
        this.logDownload(url+"下载完成");
    }

    private void checkPermission(String name){
        if (!"admin".equals(name)){
            new RuntimeException("权限校验失败");
        }
    }

    private void logDownload(String url){
        System.out.println("【记载:url】"+url);
    }
}

测试类

/**
 * @author: lpy
 * @Date: 2023/10/17
 */
public class FileDownloadTest {
    public static void main(String[] args) {
        DownloadProxy downloadProxy = new DownloadProxy("admin");
        downloadProxy.download("https://blog.devilwst.top");
    }
}

追求女孩例子

主题接口(Subject Interface)

/**
 * @author: lpy
 * @Date: 2023/10/17
 */
public interface IGiveGift {

    /**送娃*/
    void giveDolls();

    /**送花*/
    void giveFlowers();

    /**送巧克力*/
    void giveChocolate();
}

真实对象(Real Subject)

public class Pursuit implements IGiveGift{
    // 美眉
    private Girl mm;

    public Pursuit(Girl mm){
        this.mm = mm;
    }

    @Override
    public void giveDolls() {
        System.out.println("亲爱的:"+this.mm.getName()+"送你娃娃");
    }

    @Override
    public void giveFlowers() {
        System.out.println("亲爱的:"+this.mm.getName()+"送你花");
    }

    @Override
    public void giveChocolate() {
        System.out.println("亲爱的:"+this.mm.getName()+"送你巧克力");
    }
}

代理类

public class Proxy implements IGiveGift{
    private Pursuit gg;

    public Proxy(Girl mm){
        this.gg = new Pursuit(mm);
    }
    @Override
    public void giveDolls() {
        this.gg.giveDolls();
    }

    @Override
    public void giveFlowers() {
        this.gg.giveFlowers();
    }

    @Override
    public void giveChocolate() {
        this.gg.giveChocolate();
    }
}

其他类

@Data
public class Girl {
    private  String name;

    public Girl(String name){
        this.name = name;
    }
}
// 测试类
public class ProxyModeTest {
    public static void main(String[] args) {
        Girl a = new Girl("艾AA");

        Proxy proxy = new Proxy(a);
        proxy.giveChocolate();
        proxy.giveDolls();
        proxy.giveFlowers();
    }
}

应用

代理模式适用场景 :

保护目标对象 : 客户端 只与 代理类 进行交互 , 不清楚 目标对象 的具体细节 ; 相当于 租客 只与 中介 进行交互 , 不知道房东的信息 ;
增强目标对象 : 代理类 在 目标对象的基础上 , 对 目标对象的功能 进行增强 ;

代理模式优缺点

  • 优点:
    • 可以在访问真实对象之前或之后执行一些额外的操作,以实现特定的需求。

    • 分离目标对象 : 代理模式 能将 代理对象 与 真实被调用的 目标对象 分离 ;

    • 降低耦合 : 在一定程度上 , 降低了系统耦合性 , 扩展性好 ;

    • 保护目标对象 : 代理类 代理目标对象的业务逻辑 , 客户端 直接与 代理类 进行交互 , 客户端 与 实际的目标对象之间没有关联 ;

    • 增强目标对象 : 代理类 可以 在 目标对象基础上 , 添加新的功能 ;

  • 缺点:

    • 类个数增加 : 代理模式 会 造成 系统中 类的个数 增加 , 比不使用代理模式增加了代理类 , 系统的复杂度增加 ; ( 所有的设计模式都有这个缺点 )

    • 性能降低 : 在 客户端 和 目标对象 之间 , 增加了一个代理对象 , 造成 请求处理速度变慢 ;

扩展

  • 静态代理 : 在代码中 , 使用指定的代理 ; 显示的定义了一个业务实现类代理 ; 在代理类中 , 对同名的业务方法进行包装 , 用户通过调用 代理类中 被包装过的业务逻辑方法 , 来调用 被包装对象 的业务方法 , 同时对目标对象的业务方法进行增强 ;

  • 动态代理 : 由 JDK 提供 , 只能对 实现的接口的类 进行动态代理 , 不能代理具体的实现类 ; 通过 接口 中的 方法名 , 在 动态生成的 代理类 中 , 调用 业务实现类 的 同名方法 ;

JDK 动态代理 , 用到的代理类 , 是在程序调 用到代理对象时 , 由 Java 虚拟机创建 , Java 虚拟机 根据传入的 业务实现类对象 以及 方法名 , 动态地创建代理类 Class 文件 , 当该 Class 文件被字节码引擎执行 , 通过该代理类对象进行目标方法的调用 ;

动态代理无法代理类 , 只可以代理接口 ;

CGLib 代理 : 可以 针对类实现进行代理 ;
如果要 代理一个类 , CGLib 会生成一个被代理类的子类 , 通过 继承该类 并 覆盖其中的方法 ;
如果该类时 final 的 , 则无法被继承 , 如果类中的方法是 final 的 , 该方法无法被重写 ;
使用 CGLib 代理要特别注意 final 修饰符 ;

7 大设计原则

不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;

- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。

- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。

- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。

- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。

- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。

- 迪米特法则(Law of Demeter - LoD):
迪米特法则也被称为最少知识原则,它要求一个对象应该只与其密切的朋友交互,而不应该暴露太多的信息给外部对象。这有助于减少对象之间的耦合度,提高系统的灵活性。

- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。

源码位置

全部源码github.com/xs-alpha/designPattern

如有内容侵权,麻烦联系博主删除

参考文献

浙公网安备33011302000604

辽ICP备20003309号