java常见设计模式之代理模式(Proxy Model)

本文详细解析了代理模式中的静态代理和动态代理,通过租房场景生动解释了两种代理的区别,并提供了具体的Java代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、写在前面

  这次写代理模式,代理模式里面的动态代理(Dynamic Proxy)逻辑稍显混乱,不太容易理解,本章节会将代理模式里面的静态代理(Static Proxy)和动态代理(Dynamic Proxy)结合对比理解,其中动态代理的实现需要具备 Java 的反射(Reflection)知识,原理方面会结合实例和静态代理的对比尽量说清楚。

2、概述

  在某些情况下,一个客户不想或者不能直接引用一个对象,代理对象就再客户端和被代理对象之间起到中介的作用。就好比你在北京租房,初来乍到,人生地不熟,找房子遍地都是中介,想找房东可没那么容易(基本算得上是找不到房东直租的)。问题来了,找不到房东直租,但房子又是一切的基础,so....走中介,房东能租房,中介也能租房,不过是你通过中介去将房东的房子租给自己。OK,这就是一个活生生的代理模式的例子,相必在外漂泊的小年轻们都感同身受吧。

  再说说代理模式的分类,代理模式主要分为两类:静态代理(Static Proxy)、动态代理(Dynamic Proxy)。

 静态代理:一个被代理的真实对象对应一个代理,相当于一个租房中介只代理一个房东。弊端很明显,这样下去中介早饿死了!!!并且中介公司管理这么庞大的中介团队早晚逗得垮掉。反应到代码里面就是:代理类急剧增加,灵活性降低,增加了代码的复杂度。

   动态代理:动态代理主要是去解决静态代理存在的问题(及一个代理对应一个被代理对象),现实中也是这样,不可能有中介只做一个房东的生意,一个中介手里n多房子供你选择,3人间、6人间、隔断、小两居等各种房源。反应到代码里面:代理类只有一个,根据客户需求动态的去改变代理的真实对象,增加了代码的灵活性,降低了代码的复杂性。

3、目的

  代理模式的目的在于:为其他对象提供一种代理的方式控制被代理对象。

4、结构组成

  代理模式主要涉及到三个角色:抽象角色、代理角色、真实角色(被代理的角色)。

   抽象角色:声明真实对象和代理对象的共同接口。即真实对象和代理对象共同要实现的行为动作(好比房东和中介都要能够实现租房的行为,都能把房子租给你)。

   代理角色:代理角色内部含有对真实角色的引用,从而可以去操作真实对象,同时代理对象提供与真实对象的接口,以便在任何时候都能代替真实对象。同时,代理对象在执行真实对象的操作时,也能附加它自己的操作,相当于对真实对象的封装(可以理解为中介在执行将房东的房子租给你这一操作时,可以向你收取中介费,还可以在退房的时候扣你押金这类房东不具有的操作)。

   真实角色:代理角色所代理的对象,亦即我们最终要引用的对象。

5、实现

  5.1 静态代理

    静态代理主要涉及到的三个角色关系图如下:

  代理角色和真实角色都要实现同一个操作行为接口(或继承同一个抽象类),并且代理角色依赖于真实角色,因为代理角色内部有对真实角色的引用,代理在操作真实角色去执行动作时,必须要知道是哪  个真实角色。

    

下面是静态代理的实现:

 声明抽象角色和代理角色实现的共同接口。     

  package com.cnblogs.vincentzh.staticproxy;
  // 声明出租房屋接口
  public interface RentOut
  {    
      @Override
      public void rent();
 }

 创建真实角色,实现租房接口。

  package com.cnblogs.vincentzh.staticproxy;
  // 创建房主类(真实角色)
  public class HouseHolder implements RentOut
  {    
      @Override
      public void rent()
      {
          System.out.println("I’m renting the house!");
      }
 }  


创建代理角色,实现与真实角色相同的接口
  package com.cnblogs.vincentzh.staticproxy;
  // 创建代理(中介)角色,与房东实现相同接口
  public class Proxy implements RentOut
  {
      private HouseHolder houseHolder; // 代理角色内部含有真实角色的引用
          
      // 重写租房方法,添加中介操作
      @Override
      public void rent()
     {
         this.preRentOut(); // 代理对象添加自己的操作
         
         if(null == houseHolder)
         {
             houseHolder = new HouseHolder();
         }
         houseHolder.rent();
         
         this.postRentOut(); // 代理对象添加自己的操作
     }
     
     // 中介操作,租房前收中介费
     public void preRentOut()
     {
         System.out.println("I need more money!");
     }
     
     // 中介操作,租房后扣押金
     public void postRentOut()
     {
         System.out.println("I will deduct some money!");
     }
 }

 创建真实环境类,实现代理功能。

  客户对象并不知道被代理的真实对象,只知道自己需要进行某项操作,并且某个代理能帮助自己完成这一操作。(即客户要租房子,并不知道要从哪个房东手里租房,只知道中介能帮助自己租到房子,至于房子从哪个房东那里租的,就是中介(代理)的事儿了,客户并不知道,也不需要直到)。

  package com.cnblogs.vincentzh.staticproxy;
  // 租房客户类
  public class Client
  {
      /**
       * @param args
       */
      public static void main(String[] args)
      {
         // 涉世未深的小年轻要租房,苦于联系不到房东,只能通过中介
         RentOut rentOut = new Proxy();
         rentOut.rent();
     }
 }

  运行程序,客户类通过代理访问真实角色,不仅实现真实角色的操作行为,同时也添加了代理自己的操作行为。

 

 

  静态代理的弊端就在于:每当有一个真实对象需要被代理,就需要创建一个新的代理类,因为代理类内含有对真实对象的引用,代理类需要与真实对象一一对应。这样,当需要代理的真实对象比较多时,代理类就会急剧增加,增加了代码的复杂性,使代码变得尤其繁重,不易维护。那么动态代理就很好的解决了这一问题。

  5.2 动态代理

    动态代理解决了静态代理存在的弊端,实现了多个被代理真实对象仅由一个代理类代理即可。

    1)创建两个行为接口,分别实现 rent、sale 不同操作的行为;

 package com.cnblogs.vincentzh.dynamicproxy;
 // 创建租房接口
 public interface Rent
 {
     public void rent();
 }
 package com.cnblogs.vincentzh.dynamicproxy;
 // 创建售卖接口
 public interface Sale
 {
     public void sale();
 } 

    2)创建两个需要被代理的真实对象类,且他们分别实现了含有不同操作行为的接口;

  package com.cnblogs.vincentzh.dynamicproxy;
  // 创建房主类(真实角色),实现租房接口
  public class HouseHolder implements Rent
  {
      @Override
      public void rent()
      {
          System.out.println("HouseHolder: I’m renting the house!");        
      }
 }

 

  package com.cnblogs.vincentzh.dynamicproxy;
  // 创建商人类(真实对象),实现售卖接口
  public class BusinessMan implements Sale
  {
      @Override
      public void sale()
      {
          System.out.println("BusinessMan: I'm salling something!");
      }
 }

    3)创建动态代理类,只需要一个代理类,就能够实现对 n 个需代理对象的代理,从而解决了静态代理中代理与真实对象一一对应导致类的数目急剧增加的问题。

  package com.cnblogs.vincentzh.dynamicproxy;
  // 创建动态代理类,实现代码运行过程中对各种真实对象的动态代理
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Method;
  
  public class DynamicProxy implements InvocationHandler
  {
      private Object realSubject;
      
     public DynamicProxy(Object realSubject)
     {
         this.realSubject = realSubject;
     }
     
     public void setRealSubject(Object realSubject)
     {
         this.realSubject = realSubject;
     }
 
 
     @Override
     // 实现InvocationHandler接口的 invoke 方法,当代理类调用真实对象的方法时,
     // 将直接寻找执行 invoke 方法。
     public Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable
     {
         this.preRent(); // 执行代理自己添加的行为操作
         
         method.invoke(realSubject, args); // 以反射(reflection)的形式引用真实对象的方法
         
         this.postRent(); // 执行代理自己添加的行为操作
         return null;
     }
     
     // 代理类自行添加的行为
     public void preRent()
     {
         System.out.println("I need more money!");
     }
     
     // 代理类自行添加的行为
     public void postRent()
     {
         System.out.println("I will deduct some money!");
     }
 }

    4)客户调用类,只需通过改变被代理的真实对象,就能直接实现被代理的对象的更换,就不需要再去为 BusinessMan 类创建一个新的代理类了。BusinessMan类和HouseHolder类公用一个DynamicProxy动态代理类,但在做代理时,却是动态生成两个不同的代理实例(Proxy Instance),这就是所谓的动态代理。

  package com.cnblogs.vincentzh.dynamicproxy;
  //创建客户类
  import java.lang.reflect.Proxy;
  
  public class Client
  {
      /**
       * @param args
       */
     public static void main(String[] args)
     {
         HouseHolder houseHolder = new HouseHolder();
         DynamicProxy handler = new DynamicProxy(houseHolder); // 生成 HouseHolder 的代理
         
        // 动态生成代理实例(HouseHold代理实例),代理支持的接口由初始化参数(第二个)指定,代理实例处理操作所调用的 handler 由第三个参数指定
         Rent rent = (Rent)Proxy.newProxyInstance(HouseHolder.class.getClassLoader(), HouseHolder.class.getInterfaces(), handler);
         rent.rent(); // 执行客户需要进行的行为操作,动态生成的代理实例直接调用指定 handler 的 invoke 方法
         
         System.out.println("----------------------------------");
         
         BusinessMan businessMan = new BusinessMan();
         handler.setRealSubject(businessMan); // 为代理更换引用的真实对象(即原本被代理的 HouseHolder 被更换为了 BusinessMan)
         // 动态生成代理实例(BusinessMan代理实例)
         // 注:代理实例实在代码执行过程中动态执行的
         Sale sale = (Sale)Proxy.newProxyInstance(BusinessMan.class.getClassLoader(), BusinessMan.class.getInterfaces(), handler);
         sale.sale();        
     }
 }

  执行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值