【设计模式】12. 代理模式

定义

Proxy pattern provide a surrogate or  placeholder for another object to control access to it.

代理模式为其他对象提供一个(替身)或(占位符)以控制对这个对象的访问


所谓控制访问,例如:

  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的对象
  • 保护代理基于权限控制对资源的访问


类图


1、Subject 抽象主题

为RealSubject和Proxy提供了接口。

通过实现这一接口,Proxy就能在RealSubject出现的地方取代它。

2、RealSubject 具体主题

(被委托角色、被代理角色)。是业务逻辑的具体执行者,是真正做事的对象。

是被proxy代理和控制访问的对象。

3、Proxy 代理主题

(委托类、代理类)。持有RealSubject的引用,负责对RealSubject的应用。

客户和RealSubject的交互都必须通过Proxy,Proxy也控制了对RealSubject的访问。任何用到RealSubject的地方都可以用Proxy取代。


优点

职责清晰:RealSubject实现实际的业务逻辑,不用关心其他非本职的事务

高扩展性:RealSubject是随时都会发生变化的,只要他实现了接口,Proxy即可不做任何修改。


缺点


远程代理

JDK中的RMI机制(Remote Method Invocation,远程方法调用)是一种远程代理的应用。

在java中你不能取得另一个堆的对象引用,也就是说不能这么写代码:

Duke d = <另一个堆的对象>


那如何调用另一个JVM中的对象的方法呢?Java RMI通过远程代理可以办到!下图的Stub就相当于一个Proxy


1. 制作远程接口

  1. 继承java.rmi.Remote这个记号接口;
  2. 接口中声明的方法都抛出RemoteException;
  3. 确保变量和返回值都是基本类型,或可序列化。

public interface MyRemote extends Remote {
    public String sayHello() throws RemoteException;
}
——相当于Subject

2. 制作远程实现

  1. 实现上述接口;
  2. 扩展UnicastRemoteObject;
  3. 定义一个无参数构造器,抛出RemoteException;
  4. 利用RMI Registry注册此服务。

//1.实现上述接口
//2.扩展UnicastRemoteObject
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
  //注意不需要声明RemoteException
  public String sayHello() {
    return "Server says: hey!";
  }

  //3.定义一个无参数构造器,抛出RemoteException
  public MyRemoteImpl() throws RemoteException {}

  public static void main(String[] args) {
    try {
      //4.注册服务
      MyRemote service = new MyRemoteImpl();
      Naming.rebind("RemoteHello", service);
    } catch (Exception e) {

    }
  }

}

3. 产生Stub和Skeleton

在上述远程实现类MyRemoteImpl上执行rmic命令,生产MyRemoteImpl_Stub.class、MyRemoteImpl_Skel.class两个文件。


4.启动rmiregistry

确保当前目录可以访问上述类,执行rmiregistry命令。否则无法调用Naming.rebind()注册服务。


5.启动服务

java MyRemoteImpl

——当然也可以放到另一个独立的类中启动。


6.客户端代码

现在客户端需要取得Stub对象(代理),以便调用其中的方法

public class Client {
  public static void main(String[] args) {
    try {
      MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
      String s = service.sayHello();
      ...
    } catch (Exception e) {

    }
  }
}

【注】

Java5中甚至连Stub都不需要了。将RMI和动态代理搭配使用,可以动态地产生stub。


虚拟代理

虚拟代理作为创建开销大的那些对象的代表;

虚拟代理经常直到我们真正需要一个对象的时候才创建它;而在创建前和创建中,由虚拟代理来扮演对象的替身;创建后,代理就会将请求直接委托给对象。

【例】

加载网络图片,在加载过程中 显示“downloading”,在加载完成后显示图片:

//Icon类作为Subject
public class ImageProxy implements Icon {

  //这个是RealSubject!!!
  ImageIcon realIcon;

  URL imageURL;
  Thread retrievalThread;
  boolean retrieving = false;

  public ImageProxy(URL url) {imageURL = url}

  public int getIconWidth(){
    if(realIcon != null){
      return realIcon.getIconWidth();
    } else {
      return 800;
    }
  }


  public void paintIcon(...) {
    if(realIcon != null){
      //加载完成后,将请求直接委托给对象
      realIcon.paintIcon(...);
    } else {
      //加载过程中,由虚拟代理来扮演对象的替身
      print("downloading...");
      if(!retrieving){
         retrieving = true;
         retrievalThread = new Thread(new Runnable() {
            public void run(){
                //Proxy内部可以创建RealSubject!!
                realIcon = new ImageIcon(imageURL);
                。。。
            }
         });
         retrievalThread.start();
      } 
    }

  }

}

动态代理

动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。


其中Proxy由jdk提供,无需编写。我们只要提供InvocationHandler即可。

InvocationHandler的工作是响应代理的任何调用;

可以将其看成是代理收到方法调用后,请求做实际工作的对象。——但是并不是通过调用InvocationHandler的相应方法,而是调用一个方法:invoke。


【例】

世纪佳缘网站的每个用户数据是个PersonBean,要求:允许用户设置自己的个人信息,但不能设置自己的信誉度。

public class PersonBeanImpl implements PersonBean {
   String name;
   String gender;

   int rate;
   ....getter and setter
}

1. 创建InvocationHandler

分别针对调用本用户方法、调用其他用户方法,编写两个InvocationHandler

//处理调用本用户方法的Handler
public class OwnerInvocationHandler implements InvocationHandler {
  PersonBean person;

  public OwnerInvocationHandler(PersonBean person) {
    this.person = person;
  }

  @Override
  //每次proxy的方法被调用,都会导致proxy调用invoke方法
  public Object invoke(Object proxy, Method method, Object[] args) 
                throws IllegalAccessException {
    try{
        if (method.getName().equals("setRate")){ //不可修改用户自身的信誉度
           throw new IllegalAccessException();
        } else {
           return method.invoke(person, args);
        }

    } catch (InvocationTargetException e) {
      ...
    }
    return null;

  }
}

//处理调用其他用户方法的Handler
public class NoOwnerInvocationHandler implements InvocationHandler {
  PersonBean person;

  public NoOwnerInvocationHandler(PersonBean person) {
    this.person = person;
  }

  @Override
  //每次proxy的方法被调用,都会导致proxy调用invoke方法
  public Object invoke(Object proxy, Method method, Object[] args) 
                throws IllegalAccessException {
    try{
        if (method.getName().equals("setName")){//不可修改其他用户的个人信息
           throw new IllegalAccessException();
        } else {
           return method.invoke(person, args);
        }

    } catch (InvocationTargetException e) {
      ...
    }
    return null;

  }
}


2. 创建Proxy类并实例化

创建一个代理,将他的方法调用转发给OwnerInvocationHandler:

/**
     * @param loader:     the class loader to define the proxy class
     * @param interfaces: the list of interfaces for the proxy class to implement
     * @param h:          the invocation handler to dispatch method invocations to
*/
PersonBean getOwnerProxy(PersonBean person) {
  return (PersonBean) Proxy.newProxyInstance (
      person.getClass().getClassLoader(),
      person.getClass().getInterfaces(),
      new OwnerInvocationHandler(person)
  );

}

这里可以看出,PersonBean是个通用接口,相当于Subject。


3. 客户端代码

PersonBean mike = ...;

//代理
PersonBean ownerProxy = getOwnerProxy(mike);
//访问自身的个人信息
System.out.println(ownerProxy.getName());
mike.setAge(28);
//不能修改自身的信誉度
try {
  ownerProxy.setRate(1);
} catch (Exception e) {
  System.out.println("ERROR!");
}

//另一个代理
PersonBean noOwnerProxy = getNoOwnerProxy(mike);
//不可修改他人的个人信息
。。。
//可以评价他人的信誉度
。。。

保护代理

是一种根据访问权限决定客户可否访问对象的代理。

如上例,用户无权修改他人的个人信息,就是一种保护代理。


代理模式、装饰者模式

代理模式是控制对象的访问,是代表对象,不是装饰对象。

装饰者则为对象增加行为。





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值