【设计模式(12)】结构型模式之代理模式

个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充


前言

“中介”这个职业大家都知道,比如买房租房,我们一般是与中介进行交流、看房、议价等等,甚至我上一次租的房住了一年都没见过房东,同理房东也没见过我。。。

还有譬如买火车票并不需要去火车站,在携程或者12306app上就可以购买;订酒店通过美团或者携程也能够完成,并不需要本人去酒店;还有浏览那些“不存在”的网站的时候,我们需要“梯子“等等


以租房中介来说,一方面他是我的中介,代替我去跟房东谈价格谈房屋配置,谈妥之前房东并不知道我是谁,相当于我的代理。

另一方面他也是房东的中介,代替房东跟我谈价格谈合同,谈妥之前房东同样不知道我是谁,相当于房东的代理。

而且在代理的过程中,并不一定会原话转达给对方,大概率会做一些小的变动,以促成合同的完成。这是他的能力范围内的事情,当然会怎么做看个人咯。


说到底,之所以需要中介,是因为我们不想或者不能直接访问目标对象


代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。

这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。


1.介绍

使用目的:为其他对象提供一种代理以控制对这个对象的访问

使用时机:需要在访问一个类时添加一些控制

解决问题:由于某些原因客户端不能或者不想要直接访问目标对象

实现方法:添加中间层,用于实现与被代理类组合。客户端访问中间层,以间接访问被代理类

应用实例

  • Windows系统的快捷方式
  • 机票、车票、酒店等代售点
  • 房屋中介、职务中介等
  • spring中的aop(面向切面编程)

优点

  • 客户端不能直接访问目标对象,可以保护目标对象,提高安全性
  • 客户端与目标对象解耦,职责清晰,也提高了程序的扩展性
  • 代理可以扩展目标对象的功能

缺点

  • 代理模式需要在系统中新增类,提高了系统体积
  • 客户端和目标对象中增加了代理对象,会一定程度上降低执行速度
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂

使用场景:按职责来划分,通常有以下使用场景

说来话长,不多赘述

  • 远程代理
  • 虚拟代理
  • Copy-on-Write 代理
  • 保护(Protect or Access)代理
  • Cache代理
  • 防火墙(Firewall)代理
  • 同步化(Synchronization)代理
  • 智能引用(Smart Reference)代理

2.结构

代理模式的主要角色如下

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。image-20201029115801546

  • 代理类和真实主题类都需要实现接口抽象主题类,以保证其对外接口一致

  • 客户端调用代理类中的接口,实际上是对真实主题类中方法加上前置后置处理后的方法,但仍然需要调用真实主题类的方法

  • 代理类需要持有一个真实主题类对象,以调用其中的目标方法


3.实现步骤

  1. 定义抽象主题接口

    interface Subject {
        void Request();
    }
    
  2. 定义真实主体类,实现抽象主题接口

    class RealSubject implements Subject {
        @Override
        public void Request() {
            System.out.println("访问真实主题方法...");
        }
    }
    
  3. 定义代理类,实现抽象主题接口,并持有一个真实主题对象

    class Proxy implements Subject {
        private RealSubject realSubject;
    
        public Proxy() {
            this.realSubject = new RealSubject();
        }
    
        @Override
        public void Request() {
            preRequest();
            realSubject.Request();
            postRequest();
        }
    
        private void preRequest() {
            System.out.println("访问真实主题之前的预处理。");
        }
    
        private void postRequest() {
            System.out.println("访问真实主题之后的后续处理。");
        }
    }
    

    需要持有一个真实主题对象,构造这个对象的时间可以按照业务处理,一般有下面3种情况

    • 写在代理类构造函数中,构造代理类的同时构造真实主题对象
    • 写在接口中,调用接口时检查是否已构造,没有则此时构造
    • 在客户端中构造真实主题对象,通过代理类的构造函数传入代理类

完整代码

package com.company.test.proxy;

//抽象主题
interface Subject {
    void Request();
}

//真实主题
class RealSubject implements Subject {
    @Override
    public void Request() {
        System.out.println("访问真实主题方法...");
    }
}

//代理
class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy() {
        this.realSubject = new RealSubject();
    }

    @Override
    public void Request() {
        preRequest();
        realSubject.Request();
        postRequest();
    }

    private void preRequest() {
        System.out.println("访问真实主题之前的预处理。");
    }

    private void postRequest() {
        System.out.println("访问真实主题之后的后续处理。");
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.Request();
    }
}

运行结果

image-20201029120959622

4.正向代理与反向代理

都是代理,一个正一个反,那么到底是是啥反了?答案是代理的对象反了

通常说三个角色,客户端—代理—服务端

  • 正向代理是代理客户端,隐藏客户端的真实身份

    如VPN,VPN服务器会代替我们去访问目标网站。对于目标网站而言,交互的对象是代理,他并不知道代理的背后是谁,甚至不知道是不是代理。

    目的正是让服务端不知道是谁在访问。

  • 反向代理是代理服务端,隐藏服务端的真实身份

    如10086或者10010客服热线,我们拨的同一个电话,但是对面的人大概率每次都不一样,但他们都会说自己是客服,实际上我们也不关心是谁,我们只关心能不能解决自己的问题。

    很显然客服不止一个,我们拨过去后系统会选择空闲的客服接线给我们,以保证不会出现长时间排队的情况。

    虽然代理了服务端,隐藏其身份,但这个不是目的。

    目的是让代理去选择合适的服务端,只是没打算告知客户端罢了。对客户端而言,交互对象是代理,并不知道代理的对象是谁

正向代理代理的对象是客户端,反向代理代理的对象是服务端

主要是用在服务器层面的,为了避免偏题,此处不再赘述,有兴趣的可以去这两个地方看看,讲得不错

https://blog.csdn.net/lixiangss1993/article/details/87934562

https://www.zhihu.com/question/24723688


5.静态代理与动态代理

上述步骤就是静态代理,但存在以下缺陷

代理主题是为真实主题专门配备的,因此是一对一的关系,且使用同一个接口进行规范。

因此代理主题必须基于已有的真实主题,每新增一个真实主题需要新增一个代理主题。


动态代理的出现则是为了解决这种缺陷

动态代理利用反射机制在运行时创建代理类,客户端调用代理来执行服务端方法,因此一个代理类适用于所有服务端。

package com.company.test.invocation;

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

//抽象主题
interface Subject {
    void Request(String s);
}

//真实主题
class RealSubject implements Subject {
    @Override
    public void Request(String s) {
        System.out.println("访问真实主题方法...");
    }
}

//代理
class ProxyHandler implements InvocationHandler {
    private Object object;

    public ProxyHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke " + method.getName());
        method.invoke(object, args);
        System.out.println("After invoke " + method.getName());
        return null;
    }
}

public class InvocationTest {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject realSubject = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyHandler(subject));
        realSubject.Request("hello?");
    }
}

image-20201029155739485

通过这样的处理方式,我们将subject托管给ProxyHandler代理。

我们可以在invoke方法中进行前置处理,后置处理,甚至修改参数(args),或者直接抛出异常

实际使用的过程中,我们通常使用AOP(面向切面编程)以注解的方式进行代理,有兴趣的可以看看之前我整理的JWT的自定义切面拦截器

https://blog.csdn.net/qq_25978501/article/details/108409031


后记

代理模式真要学完可能得几天时间,有空再整理细说吧,先做了解,实战中会遇到不少的,特别是AOP几乎算是面试必问的了


作者:Echo_Ye

WX:Echo_YeZ

Email :echo_yezi@qq.com

个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

原文地址:https://www.cnblogs.com/silent-bug/p/13897377.html