zoukankan      html  css  js  c++  java
  • Java设计模式-代理模式

    代理设计模式

    代理设计模式:为其他对象提供一种代理以控制对这个对象的访问。
    所谓代理,就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
    代理设计模式结构类图:
    代理设计模式结构类图
    代理模式中类或接口的作用:

    • 抽象主题角色(Subject):可以使抽象类也可以是接口,是一个最普通的业务类型定义。声明了具体主题角色和代理主题角色的共同接口。
    • 具体主题角色(RealSubject):也叫作被委托角色、被代理角色。它是冤大头,是业务逻辑的实际执行者。
    • 代理主题角色(Proxy):也叫作委托类、代理类。它负责对真实主题的应用,代理主题角色内部含有具体主题角色的引用,从而可以在任何时候操作目标对象;代理主题角色提供一个与具体主题角色相同的接口,以便可以在任何时候替代具体主题角色。代理主题角色通常会在客户端调用传递给具体主题角色之前或之后,执行一些其他的操作,而不是单纯地将调用传递给目标对象。

    什么时候使用代理模式?当我们不想直接操作目标类或者想对目标类的方法访问做一些额外的操作时可以考虑使用代理模式。

    代码模型如下:

    // 抽象主题类:定义一个request方法
    interface Subject{
        public void request();
    }
    // 真实主题类:正常的业务实现类,实现了Subject接口
    class RealSubject implements Subject{
    
        public void request() {
            System.out.println("RealSubject request");
        }
    }
    
    class Proxy implements Subject{
    
        private Subject subject;
    
        public Proxy(Subject subject) {
            this.subject = subject;
        }
    
        @Override
        public void request() {
            doBefore();
            subject.request();
            doAfter();
        }
        // 预处理
        private void doBefore(){
            System.out.println("RealSubject request before");
        }
        // 善后处理
        private void doAfter(){
            System.out.println("RealSubject request after");
        }
    }

    这里面的doBefore和doAfter方法,是在调用方法时做的一些预先处理和善后处理的工作。

    代理的实现

    以玩游戏为例,比如我们要玩英雄联盟(LOL)打排位,前提是要达到30级,于是我们不想自己去打,就可以找代练来玩(代练这个行业利润非常可观)。具体的使用代码来描述为:

    interface IGamePlayer{
        void playGame();
    }
    
    class GamePlayer implements IGamePlayer{
        private String name;
        public GamePlayer(String name) {
            this.name = name;
        }
        public void playGame() {
            System.out.println(name + "正在玩");
        }
    }
    
    class GamePlayerProxy implements IGamePlayer{
    
        private IGamePlayer gamePlayer;
    
        public GamePlayerProxy(IGamePlayer gamePlayer) {
            this.gamePlayer = gamePlayer;
        }
    
        @Override
        public void playGame() {
            gamePlayer.playGame();
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            IGamePlayer gamePlayer = new GamePlayer("张三");
            GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(gamePlayer);
            gamePlayerProxy.playGame();
        }
    }

    没有直接使用真实角色GamePlayer的实例照样达到了玩游戏的效果,是的,有人在给你代练。这个代练的角色就是GamePlayerProxy 类。

    普通代理

    在上面例子中,我们new了一个真是的角色GamePlayer然后将其传给代理类GamePlayerProxy,这样就不需要让真实的角色来干活了,所做的操作都由代理来做。普通代理要求客户端只能访问代理角色,而不能访问真是的角色。

    /**
     * 
     * 普通代理:我们要知道代理的存在然后才能访问
     * 要求客户端只能访问代理角色,而不能访问真实角色
     * @author qhyuan1992
     *
     */
    interface IGamePlayer{
        void playGame();
    }
    
    class GamePlayer implements IGamePlayer{
        private String name;
        public GamePlayer(String name) {
            this.name = name;
        }
        public void playGame() {
            System.out.println(name + "正在玩");
        }
    }
    
    class GamePlayerProxy implements IGamePlayer{
    
        private IGamePlayer gamePlayer;
    
        public GamePlayerProxy(String name) {
            gamePlayer = new GamePlayer("张三");
        }
    
        @Override
        public void playGame() {
            gamePlayer.playGame();
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            GamePlayerProxy gamePlayerProxy = new GamePlayerProxy("张三");
            gamePlayerProxy.playGame();
        }
    }

    所做的改变就是在代理的构造函数中指定要代理的类。这样代码更加简洁,对调用者来说,只需要知道代理的存在而不需要知道代理谁。屏蔽了真实主题角色的变更对上层代码的影响,真实主题角色想怎么改变就怎么改变对上层不会有任何影响。

    强制代理

    一般是通过代理找到真实的角色,但是强制代理却是要强制,必须通过真实的角色查找到代理角色,否则你不能访问他的方法:不管是通过代理类还是通过真实角色。

    /**
     * 强制代理:必须通过真是的角色查找到代理角色,否则你不能访问。
     * 不管是通过代理类还是通过真实角色都无法访问
     * @author qhyuan1992
     *
     */
    interface IGamePlayer{
        void playGame();
        public IGamePlayer getProxy();
    }
    
    class GamePlayer implements IGamePlayer{
    
        private String name;
        private IGamePlayer proxy;
    
        public GamePlayer(String name) {
            this.name = name;
        }
    
        public void playGame() {
            if (isProxy()) {
                System.out.println(name + "正在玩");
            }else {
                System.out.println("请使用指定的代理访问");
            }
        }
    
        public IGamePlayer getProxy() {
            proxy = new GamePlayerProxy(this);
            return proxy;
        }
    
        private boolean isProxy(){
            return this.proxy != null;
        }
    }
    
    class GamePlayerProxy implements IGamePlayer{
    
        private IGamePlayer gamePlayer;
    
        public GamePlayerProxy(IGamePlayer gamePlayer) {
            this.gamePlayer = gamePlayer;
        }
    
        @Override
        public void playGame() {
            gamePlayer.playGame();
        }
    
        @Override
        public IGamePlayer getProxy() {
            return this;
        }
    }
    
    public class Test {
        public static void main(String[] args) {
    
            // 1. 无法访问:使用真实角色
            // 他要求你必须用代理来访问,你使用真实角色直接来访问肯定无法访问
            IGamePlayer gamePlayer = new GamePlayer("张三");
            gamePlayer.playGame();
    
            // 2. 无法访问:使用代理类
            // 自己new出来的代理,而不是他指定的代理
            IGamePlayer gamePlayer = new GamePlayer("张三");
            GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(gamePlayer);
            gamePlayerProxy.playGame();
    
            // 3. 可以访问:使用真实角色返回的代理
            IGamePlayer gamePlayer = new GamePlayer("张三");
            GamePlayerProxy proxy = (GamePlayerProxy) gamePlayer.getProxy();
            proxy.playGame();
        }
    }

    动态代理

    在前面我们自己写的代理的方式就是静态代理。
    静态代理存在的问题:试想如果Subject接口发生变化。那么代理类和具体的目标实现都要变化,不是很灵活。
    动态代理指的是现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
    而使用Java内建的对代理模式支持的功能来实现则没有问题。

    JDK1.3提供了对动态代理的支持,InvocationHandler接口是JDK提供的动态代理接口,对被代理类的方法进行代理。

    动态代理的好处:

    • 动态代理跟静态代理相比,明显的变化是:静态代理实现的时候,在Subject接口上定义很多方法,代理类里面自然要实现很多方法;而动态代理实现的时候虽然Subject定义了很多方法,但是动态代理类始终只有一个invoke方法,这样,当Subject接口发生变化时。动态代理的接口就不需要跟着变化。
    • 动态代理可以更方便的对请求进行任何处理。
    /**
     * 动态代理:是现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
     * 自己写代理的方式就是静态代理
     * @author qhyuan1992
     */
    interface IGamePlayer{
        void playGame();
    }
    
    class GamePlayer implements IGamePlayer{
        private String name;
        public GamePlayer(String name) {
            this.name = name;
        }
        public void playGame() {
            System.out.println(name + "正在玩");
        }
    }
    
    class GamePlayIH implements InvocationHandler{
        // 被代理实例
        Object obj;
        // 我要代理谁
        public GamePlayIH(Object obj) {
            this.obj = obj;
        }
    
        // 调用被代理的方法
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            doBefore();
            Object result = method.invoke(obj, args);
            doAfter();
            return result;
        }
    
        private void doAfter() {
            System.out.println("调用后");
        }
    
        private void doBefore() {
            System.out.println("调用前");
        }
    }
    
    public class Test {
    
        public static void main(String[] args) {
            IGamePlayer player = new GamePlayer("张三");
            InvocationHandler handler = new GamePlayIH(player);
            IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(player.getClass().getClassLoader(), new Class[]{IGamePlayer.class}, handler);
            proxy.playGame();
        }
    }
    

    输出:

    调用前
    张三正在玩
    调用后

    参考:设计模式之禅/JAVA与模式

  • 相关阅读:
    使用InternalsVisibleTo给assembly添加“友元assembly”
    从.snk文件导出密钥
    解决Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COER
    MySQL max_allowed_packet设置及问题
    MySQL导入sql脚本错误:2006
    执行nova-manage db sync时出错,提示’Specified key was too long; max key length is 1000 bytes’
    MySQL编码latin1转utf8
    微软为何选择在 Github 上开源 .NET 核心?
    In House打包流程
    GetBuiltProjectOutputRecursive error running Xamarin Forms iOS on Visual Studio
  • 原文地址:https://www.cnblogs.com/qhyuan1992/p/5385292.html
Copyright © 2011-2022 走看看