在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

1. 代理模式

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例

  1. Windows 里面的快捷方式。
  2. 猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。
  3. 买火车票不一定在火车站买,也可以去代售点。
  4. 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
  5. spring aop。

优点

  1. 职责清晰。
  2. 高扩展性。
  3. 智能化。

缺点

  1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

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

  1. 远程代理。
  2. 虚拟代理。
  3. Copy-on-Write 代理。
  4. 保护(Protect or Access)代理。
  5. Cache代理。
  6. 防火墙(Firewall)代理。
  7. 同步化(Synchronization)代理。
  8. 智能引用(Smart Reference)代理。

注意事项

  1. 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
  2. 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

主要涉及到以下几个核心角色

  • 抽象主题(Subject):
    • 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
  • 真实主题(Real Subject):
    • 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问。
  • 代理(Proxy):
    • 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等。
  • 客户端(Client):
    • 使用抽象主题接口来操作真实主题或代理主题,不需要知道具体是哪一个实现类。

2. 实例

我们将创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。

ProxyPatternDemo 类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。

代理模式的 UML 图

步骤 1

创建一个接口。

public interface Image {
   void display();
}

步骤 2

创建实现接口的实体类。

public class RealImage implements Image {
 
   private String fileName;
 
   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }
 
   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }
 
   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}
public class ProxyImage implements Image{
 
   private RealImage realImage;
   private String fileName;
 
   public ProxyImage(String fileName){
      this.fileName = fileName;
   }
 
   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}

步骤 3

当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。

public class ProxyPatternDemo {
   
   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");
 
      // 图像将从磁盘加载
      image.display(); 
      System.out.println("");
      // 图像不需要从磁盘加载
      image.display();  
   }
}

步骤 4

执行程序,输出结果:

Loading test_10mb.jpg
Displaying test_10mb.jpg

Displaying test_10mb.jpg

3. JAVA 中的动态代理

3.1 介绍

  • 概念
    • 静态代理:有一个类文件描述代理模式,代理对象会形成一个类。
    • 动态代理:在内存中形成代理类,一般使用这种
  • 实现方式
    • 代理对象和真实对象实现相同的接口,创建出的代理对象用此接口接收。
    • 代理对象 = Proxy.newProxyInstance();
    • 使用代理对象调用方法。
    • 增强方法
  • 增强方式
    • 增强参数列表
    • 增强返回值类型
    • 增强方法体执行逻辑

3.2 实例

真实对象实现的接口:

package rainsheep;

public interface SaleComputer {
    public String sale(double money);
    public void show();
}

真实对象的类:

package rainsheep;

public class Lenovo implements SaleComputer{

    @Override
    public String sale(double money) {
        System.out.println("花了"+money+"元买了一台联想电脑...");
        return "联想电脑";
    }

    @Override
    public void show() {
        System.out.println("展示电脑....");
    }
}

创建代理对象:

package rainsheep;

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

public class ProxyTest {

    public static void main(String[] args) {
        //真实对象
        Lenovo lenovo = new Lenovo();
        //创建代理对象
        SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //以增强sale方法为例,如果执行的方法是sale方法
                if (method.getName().equals("sale")) {
                    //sale方法的参数有一个且为double类型
                    double money = (double) args[0];
                    //对参数进行增强
                    money = money * 0.85;
                    //使用真实对象调用该方法,相当于执行方法,传入增强后的参数
                    //返回值转化为真实对象方法的返回值类型
                    String ans = (String) method.invoke(lenovo, money);
                    //返回的即为调用代理对象的此方法,所返回的值
                    //对返回值更改,增强返回值
                    return ans+"+鼠标垫";
                    //可以在返回之前写代码增强执行逻辑
                }
                else {
                    //对于别的方法,不增强,则直接调用真实对象执行方法
                    Object ans = method.invoke(lenovo, args);//没有返回值则 ans 为null
                    return ans;
                }
            }
        });
        String computer = proxy_lenovo.sale(8000);
        System.out.println(computer);

        //proxy_lenovo.show();
    }
}