• 《设计模式之禅》读书笔记(五)之代理模式


    1. 代理模式的定义和应用

    1.1 代理模式的定义

    Provide a surrogate or placeholder for another object to control access to it .(为其他对象提供一种代理以控制对这个对象的访问)

    代理模式的类图如下:

    代理模式也叫做委托模式,是一种基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。

    代理模式中的三个角色的定义:

    Subject 抽象主题角色

    抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。

    RealSubject

    也叫做被委托角色、被代理角色。是业务逻辑的具体执行者。

    Proxy

    也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

    Subject抽象主题类的通用源码:

    1. 抽象主题类
    public interface Subject{
      //定义一个方法
      public void request();
    }
    
    1. 真实主题类
    public class RealSubject implements Subject{
      //实现方法
      public void request(){
        //业务逻辑
      }
    }
    
    1. 代理类
    public class Proxy implements Subject{
      //要代理哪个实现类
      private Subject subject=null;
      //默认被代理者
      public Proxy(){
        this.subject=new Proxy();
      }
      //通过构造函数传递代理者
      public Proxy(Object ..objects){
       
      }
      //实现接口中定义的方法
      public void request(){
        this.before();
        this.subject.request();
        this.after();
      }
      //预处理
      private void before(){
        //do something
      }
      //善后处理
      private void after(){
        //do something
      }
    }
    

    一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题角色,是由场景类决定的。

    1.2 代理模式的应用

    代理模式的优点:
    1. 职责清晰

      真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

    2. 高扩展性

      具体主题角色是随时都会发生变化的,只要它实现了接口,代理类就能在不做任何修改的情况下使用。

    3. 智能化

    代理模式的使用场景:

    Spring 中的AOP

    2.代理模式的扩展

    2.1普通代理

    普通代理要求客户端只能访问代理角色,而不能访问真实角色。

    抽象游戏者
    /**
     * @Title: IGamePlayer
     * @description: 
     * @author: gaoyakang
     * @date: 2018年1月22日 下午10:31:09
     * 
     */
    public interface IGamePlayer {
    	public void login(String user,String password);
    	public void killBoss();
    	public void upgrade();
    }
    
    
    普通代理的游戏者
    /**
     * @Title: GamePlayer
     * @description: 
     * @author: gaoyakang
     * @date: 2018年1月22日 下午10:32:13
     * 
     */
    public class GamePlayer implements IGamePlayer {
    	private String name="";
    	public GamePlayer(IGamePlayer _gamePlayer,String _name) throws Exception {
    		if(_gamePlayer==null) {
    			throw new Exception("不能创建真实角色!");
    		}else {
    			this.name=_name;
    		}
    	}
    
    	public void login(String user, String password) {
    		System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
    	}
    
    	public void killBoss() {
    		System.out.println(this.name+"在打怪!");
    	}
    
    	public void upgrade() {
    		System.out.println(this.name+"又升了一级!");
    	}
    
    }
    
    
    
    普通代理的代理者
    /**
     * @Title: GamePlayerProxy
     * @description: 
     * @author: gaoyakang
     * @date: 2018年1月22日 下午10:39:45
     * 
     */
    public class GamePlayerProxy implements IGamePlayer{
    	private IGamePlayer gamePlayer=null;
    	public GamePlayerProxy(String name) {
    		try {
    			gamePlayer=new GamePlayer(this,name);
    		}catch (Exception e) {
    			// TODO: handle exception
    		}
    	}
    
    	public void login(String user, String password) {
    		this.gamePlayer.login(user, password);
    	}
    
    	public void killBoss() {
    		this.gamePlayer.killBoss();
    	}
    
    	public void upgrade() {
    		this.gamePlayer.upgrade();
    	}
    
    }
    
    
    场景类
    import java.util.Date;
    /**
     * @Title: Client
     * @description: 
     * @author: gaoyakang
     * @date: 2018年1月23日 下午10:51:09
     * 
     */
    public class Client {
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		IGamePlayer proxy=new GamePlayerProxy("张三");
    		System.out.println("开始时间是:"+new Date());
    		proxy.login("zhangsan", "admin");
    		proxy.killBoss();
    		proxy.upgrade();
    		System.out.println("结束时间是:"+ new Date());
    	}
    }
    
    

    与通用代理模式不同地方在于,普通代理在使用时代理角色只需要传入代理者的名字,无需创建对象。换句话说,被代理人对象需要在代理者类中创建,也就实现了被代理人必须知道代理人的存在。实际项目中一般通过约定禁止new一个真实的角色来达到以上目的。

    2.2 强制代理

    强制代理类要求必须通过真实角色找到代理角色。

    直观的感受是,强制代理需要你先创建一个真实角色player并通过player的getProxy()方法来获取代理。由于代理类和真实角色都继承自IGamePlayer,所以代理类和真实角色都可以再获取他们各自的代理。

    接口类
    public interface IGamePlayer {
    	public void login(String user,String password);
    	public void killBoss();
    	public void upgrade();
    	public IGamePlayer getProxy();
    }
    
    
    真实角色类
    public class GamePlayer implements IGamePlayer {
    	private String name = "";
    	private IGamePlayer proxy = null;
    
    	public GamePlayer(String _name) {
    		this.name = _name;
    	}
    
    	@Override
    	public void login(String user, String password) {
    		if (isProxy()) {
    			System.out.println("登录名为" + user + "的用户" + this.name + "登录成功!");
    		} else {
    			System.out.println("请使用指定的代理访问");
    		}
    
    	}
    
    	@Override
    	public void killBoss() {
    		if (isProxy()) {
    			System.out.println(this.name + "在打怪!");
    		} else {
    			System.out.println("请使用指定的代理访问");
    		}
    	}
    
    	@Override
    	public void upgrade() {
    		if (isProxy()) {
    			System.out.println(this.name + "又升了一级!");
    		} else {
    			System.out.println("请使用指定的代理访问");
    		}
    	}
    
    	@Override
    	public IGamePlayer getProxy() {
    		this.proxy = new GamePlayerProxy(this);
    		return this.proxy;
    	}
    
    	private boolean isProxy() {
    		if (this.proxy == null) {
    			return false;
    		} else
    			return true;
    	}
    
    }
    
    
    代理类
    public class GamePlayerProxy implements IGamePlayer{
    	private IGamePlayer gamePlayer=null;
    	public GamePlayerProxy(IGamePlayer _gamePlayer) {
    		this.gamePlayer=_gamePlayer;
    	}
    
    	@Override
    	public void login(String user, String password) {
    		this.gamePlayer.login(user, password);
    	}
    
    	@Override
    	public void killBoss() {
    		this.gamePlayer.killBoss();
    	}
    
    	@Override
    	public void upgrade() {
    		this.gamePlayer.upgrade();
    	}
    
    	@Override
    	public IGamePlayer getProxy() {
    		return this;
    	}
    
    }
    
    

    具体使用的时候,代理的获取方式跟普通代理类有些不同。如果你直接new一个代理类,那么是无法获取到正确的操作的,你必须用真实角色的getProxy()方法来获取它的代理。这就是指定的代理。

    		IGamePlayer player=new GamePlayer("张三");
    		IGamePlayer proxy=player.getProxy();
    

    完整的场景类代码如下:

    场景类
    public class Client {
    	public static void main(String[] args) {
    		IGamePlayer player=new GamePlayer("张三");
    		IGamePlayer proxy=player.getProxy();
    		System.out.println("开始时间是:"+new Date());
    		proxy.login("zhangsan", "admin");
    		proxy.killBoss();
    		proxy.upgrade();
    		System.out.println("结束时间是:"+ new Date());
    	}
    }
    

    2.3 代理是有个性的

    上面的例子中,代理类实现的接口跟真实角色是一样的。代理这个名字,就意味着我们可以通过代理实现我们在真实角色类中无法或者不想实现的方法。因此代理类应该实现跟真实角色类不同的功能,这就是代理的个性之处。

    代理的目的是在目标对象方法的基础上做增强,这种增强的本质通常就是对目标对象的方法进行过滤和拦截。

    按书中的例子我们实现一个代理收费的功能。

    类图如下:

    以强制代理类为基础,我们实现上述功能。

    代理类的接口
    public interface IProxy {
    	public void count();
    }
    
    实现了收费功能的代理类
    public class GamePlayerProxy implements IGamePlayer,IProxy{
    	private IGamePlayer gamePlayer=null;
    	public GamePlayerProxy(IGamePlayer _gamePlayer) {
    		this.gamePlayer=_gamePlayer;
    	}
    
    	@Override
    	public void login(String user, String password) {
    		this.gamePlayer.login(user, password);
    	}
    
    	@Override
    	public void killBoss() {
    		this.gamePlayer.killBoss();
    	}
    
    	@Override
    	public void upgrade() {
    		this.gamePlayer.upgrade();
    		this.count();
    	}
    
    	@Override
    	public IGamePlayer getProxy() {
    		return this;
    	}
    
    	@Override
    	public void count() {
    		System.out.println("升级费用150元");		
    	}
    
    }
    //运行结果
    //开始时间是:Tue Jan 30 22:58:14 CST 2018
    //登录名为zhangsan的用户张三登录成功!
    //张三在打怪!
    //张三又升了一级!
    //升级费用150元
    //结束时间是:Tue Jan 30 22:58:14 CST 2018
    
    

    真实角色类和Client端都无需任何改动,运行Client之后会发现果然实现了收费的功能。

    看到这里我想到了AOP。我们知道AOP的主要作用就是不改动现有代码而为其增加功能,如打印日志。上面代码实现收费功能时并没有改动真实角色,但是我们依然实现了增加功能的效果。看起来是有点AOP的影子,但是我们都知道Spring 中的AOP,使用的时候并不是在写代码的时候单独写代理类,而是在运行的过程中才指定代理去实现功能。AOP就是这样定义的:

    这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

    那么如何实现动态的代理呢?下面我们将会了解代理模式中最重要的一个扩展——动态代理。

    2.4 动态代理

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

    动态代理类图中增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。

    动态代理类
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    public class GamePlayIH implements InvocationHandler{
    
    	Class cls=null;
    	Object obj=null;
    	public GamePlayIH(Object _obj) {
    		this.obj=_obj;
    	}
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		Object result=method.invoke(this.obj, args);
         	if(method.getName().equalsIgnoreCase("login")) {
    			System.out.println("有人在用我的账号登录!");
    		}
    		return result;
    	}
    }
    

    其中invoke 方法时接口InvocationHandler定义必须实现的,它完成对真实方法的调用。动态代理根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,那么动态代理怎么才能实现被代理接口中的方法呢?通过InvocationHandler接口,所有方法都有该Handler来进行处理,也就是所有被代理的方法都由InvocationHandler接管实际的处理任务。

    我们在动态代理类中增加了一个检验登录的功能,这样更能直观的体会到动态代理的好处。

    动态代理的场景类
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    import java.util.Date;
    public class Client {
    	public static void main(String[] args) {
    		IGamePlayer player=new GamePlayer("张三");
    		InvocationHandler handler=new GamePlayIH(player);
    		ClassLoader c1=player.getClass().getClassLoader();
    		IGamePlayer proxy=(IGamePlayer)Proxy.newProxyInstance(c1, new Class[] {IGamePlayer.class}, handler);
    		System.out.println("开始时间是:"+new Date());
    		proxy.login("zhangsan", "admin");
    		proxy.killBoss();
    		proxy.upgrade();
    		System.out.println("结束时间是:"+ new Date());
    	}
    }
    
    //output:
    //开始时间是:Fri Feb 02 23:22:04 CST 2018
    //登录名为zhangsan的用户张三登录成功!
    //有人在用我的账号登录!
    //张三在打怪!
    //张三又升了一级!
    //结束时间是:Fri Feb 02 23:22:04 CST 2018
    
    

    2.5 动态代理类的通用模型

    动态代理类的通用类图如下:

    动态代理实现代理的职责,业务逻辑Subject 实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。通知Adivice 从另一个切面切入,最终在高层模块Client进行耦合,完成逻辑的封装任务。

    抽象主题
    public interface Subject {
    	public void doSomething(String str);
    }
    
    真实主题
    public class RealSubject implements Subject{
    	public void doSomething(String str) {
    		System.out.println("do something!---->"+str);		
    	}
    }
    
    动态代理的Handler类
    public class MyInvocationHandler implements InvocationHandler{
    	//被代理的对象
    	private Object target=null;
    	public MyInvocationHandler(Object _obj) {
    		this.target=_obj;
    	}
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		return method.invoke(this.target, args);
    	}
    }
    
    通知接口及实现
    public interface IAdvice {
    	public void exec();
    }
    public class BeforeAdvice implements IAdvice{
    	public void exec() {
    		System.out.println("我是前置通知,我被执行了!");
    	}
    }
    
    动态代理类
    public class DynamicProxy<T> {
    	public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {
    		if(true) {
    			(new BeforeAdvice()).exec();
    		}
    		return (T)Proxy.newProxyInstance(loader, interfaces, h);
    	}
    }
    
    
    具体业务的动态代理
    public class SubjectDynamicProxy extends DynamicProxy{
    	public static <T> T newProxyInstance(Subject subject) {
    		ClassLoader loader=subject.getClass().getClassLoader();
    		Class<?>[] classes=subject.getClass().getInterfaces();
    		InvocationHandler handler=new MyInvocationHandler(subject);
    		return newProxyInstance(loader, classes, handler);
    	}
    }
    
    动态代理的场景类
    public class Client {
    	public static void main(String[] args) {
    		Subject subject=new RealSubject();
    		Subject proxy=SubjectDynamicProxy.newProxyInstance(subject);
    		proxy.doSomething("Finish");
    	}
    }
    

    如果你对Java中的反射有所了解的话,上面的代码理解起来应该不难。我们知道,反射就是把java类中的各种成分映射成一个个的Java对象。Java在运行时会生成.class文件,反射的作用就是在程序运行时动态得.class文件中解析出类中的对象和方法。所以要实现在不修改现有代码的基础上添加功能我们肯定应该想到用反射去实现。

    3.代理模式的应用

    代理模式应用十分广泛,其中最贴近我们的就是AOP了,我会在以后的文章中详细分析AOP。

  • 相关阅读:
    Android比较实用的属性
    软件版本命名规则
    Dhroid框架笔记(DhNet、Adapter)
    Activity对话框
    Dhroid框架笔记(IOC、EventBus)
    Eclipse快捷键
    解析Excel_Jxl
    面试题
    java基础——值传递和应用传递
    java基础——子类继承父类程序执行顺序
  • 原文地址:https://www.cnblogs.com/rever/p/8417431.html
Copyright © 2020-2023  润新知