代理模式-访问对象的代理而非其本身

公号:码农充电站pro
主页:https://codeshellme.github.io

本篇来介绍代理模式Proxy Design Pattern),通过代理模式可以控制和管理对象的访问。

1,代理模式

代理模式为对象提供一个代理,来控制对该对象的访问。代理代表了原始对象,而不是真实的对象本身。

代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。

代理模式的类图如下:

在这里插入图片描述

RealSubject 是原始类,Proxy 代理类,它们都实现了 Subject 接口,这使得代理类可以取代原始类。

原始类通常在代理类的后面,客户端不会直接访问原始类,而是只访问代理类。代理类在收到客户端的请求之后,会替客户端去访问原始类,然后将结果返回给客户端。

在这里插入图片描述

代理类中保存了原始类对象的引用,代理类的实际操作还是通过调用原始类来完成,但代理类除了完成原始类的基本功能之外,还可以添加一些其它必要的功能。

这看起来,代理模式很像装饰器模式,但它们的设计意图是不一样的。代理模式的目的是为了控制和管理原始对象的访问;而装饰器模式是为了增强原有对象的能力,而且往往一个对象会被一个或多个装饰器多次包装。

2,代理模式示例

下面通过一个简单的例子,来看下如何使用代理模式。

假如我们现在有一个类 Server

class Server {
    public void handleRequest() {
        // 处理过程
        System.out.println("handle client request.");
    }
}

Server 类中有一个 handleRequest 方法,用于处理客户端请求。

现在我们想统计处理客户端请求需要的时间,最简单直接的做法是在 handleRequest 方法中添加计算时间的代码,如下:

public void handleRequest() {
    long startTime = System.currentTimeMillis();

	// 处理过程
    System.out.println("handle client request.");

    long endTime = System.currentTimeMillis();

    long reqTime = endTime - startTime;
    System.out.println(reqTime);
}

这样做的缺点是,在正常的业务处理流程中,添加了一些无关代码,使得统计代码与业务代码耦合在一起。

这种情况,就可以使用代理模式来处理,而无需改动原有代码。

首选创建一个接口 ServerInterface,让代理类和原始类都继承该接口。

interface ServerInterface {
    void handleRequest();
}

原始类实现 ServerInterface 接口,Server 只需专注业务处理:

class Server implements ServerInterface {
    public void handleRequest() {
        // 处理过程
        System.out.println("handle client request.");
    }
}

下面创建代理类,负责统计时间:

class ServerProxy implements ServerInterface {
    private Server server;

    public ServerProxy(Server server) {
        this.server = server;
    }

    public void handleRequest() {
        long startTime = System.currentTimeMillis();

        // 调用原始类
        server.handleRequest();

        long endTime = System.currentTimeMillis();

        long reqTime = endTime - startTime;
        System.out.println(reqTime);
    }
}

ServerProxy 中保存了 Server 对象的引用,会在必要的时候调用原始类,从而完成原始类的功能。

之后,客户端只需要与代理交互,而不需要跟原始类交互。

可以看到,这种实现方式并没有直接修改 Server,而是创建了 Server 的代理,避免给 Server 带来不必要的麻烦。

使用继承的方式来实现代理

上面这种代理的实现方式,使用的是组合的方式,也就是代理类中保存了一个原始类对象的引用。

如果在实际的项目中,原始类来自第三方库,这样就不能让原始类实现一个接口,因为我们不能修改第三方库。

此时,为了使用代理模式,可以继承的方式,也就是让代理类继承原始类,如下:

class ServerProxy extends Server {
    public void handleRequest() { 
        long startTime = System.currentTimeMillis();

        // 调用原始类
        super.handleRequest();

        long endTime = System.currentTimeMillis();

        long reqTime = endTime - startTime;
        System.out.println(reqTime);
    }
}

此时,代理模式的类图就变成了下面这样:

在这里插入图片描述

3,代理模式的应用

代理模式有很多的应用场景,下面来看一些常用的。

远程代理

远程代理用于控制访问远程对象,客户端与原始类在不同的地址空间中,远程代理通过网络来为客户端提供服务。

远程代理的架构图如下:

在这里插入图片描述

上图中的客户辅助对象就是代理,客户端将请求发给代理,代理将请求通过网络转发给服务辅助对象。

服务辅助对象接收代理的请求后,再将请求转给真实的服务对象,服务对象处理完请求后,再将处理结果一步步的传给客户端。

Java 中可以通过 RMI 来构建远程代理服务。

虚拟代理

虚拟代理用于控制访问创建开销大的资源,它作为创建开销大的对象的代表。

在虚拟代理中,只有我们真正需要一个对象的时候,才会创建它。

在对象创建完成之前,由虚拟代理来处理客户端的访问;在对象创建之后,虚拟代理将客户端的请求委托给真实对象处理。

保护代理

保护代理基于访问权限来控制对资源的访问,保护代理可以不让客户端访问某些资源。

防火墙代理

防火墙代理用于控制网络资源的访问,使资源免于“坏客户”的攻击。

智能引用代理

当资源被引用时,计算资源被引用的次数。

缓存代理

为开销大的资源提供暂存服务,以减少计算和网络延迟。

4,动态代理

代理模式有一个缺点,就是需要在代理类中实现原始类的所有方法,而且如果原始类很多的话,就需要创建很多的代理类,从而导致项目中类的数量倍增。如果代理类的功能相近的话,还会导致许多重复代码。

为了解决这个问题,可以使用动态代理。动态代理不需要事先为每个原始类编写代理类,而是在运行时,动态的为原始类创建代理类,然后用代理类来替换原始类。

Java 中可以使用 java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口来实现动态代理,其中用到了Java 反射的原理。

5,总结

代理模式提供了一个原有服务的代理,这样使得客户不直接访问原有服务,而是通过代理间接访问原服务,达到了控制原有服务访问的目的。

代理模式可以用组合与继承两种方式来实现。代理模式有时候会导致代理类过多和代码重复的问题,这个问题可以用动态代理来解决。

有时候代理模式看起来像是装饰者模式,但它们的设计意图是不一样的。

代理模式的应用场景有很多,比如远程代理,虚拟代理,保护代理等。

(本节完。)

推荐阅读:

适配器模式-让不兼容的接口得以适配

外观模式-简化子系统的复杂性

模板方法模式-封装一套算法流程

迭代器模式-统一集合的遍历方式

状态模式-将状态和行为封装成对象

欢迎关注作者公众号,获取更多技术干货。

码农充电站pro

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结