代理模式

目录

1.游戏代练

2.代理模式

2.1 代理模式的优点

2.2 代理模式的扩展

2.2.1 普通代理

2.2.2 强制代理

3.动态代理

4.最佳实战


1.游戏代练

现在的游戏代练公司非常多,我们可以把自己的账号交给代练人员,由他们去帮我们升级打怪。我们来修改一下类图:

实例代码如下:

IGamePlayer接口:

public interface IGamePlayer {
    /**
     * 游戏登录
     *
     * @param name
     * @param password
     */
    void login(String user, String password);

    /**
     * 杀怪
     */
    void killBoss();

    /**
     * 升级
     */
    void upgrade();
}

GamPlayer实现类:

public class GamePlayer implements IGamePlayer {
    private String name;

    public GamePlayer(String name) {
        this.name = name;
    }

    @Override
    public void login(String user, String password) {
        System.out.println(user + "登录成功");
    }

    @Override
    public void killBoss() {
        System.out.println(this.name + "在打怪");
    }

    @Override
    public void upgrade() {
        System.out.println(this.name + "升了一级");
    }
}

代理类:

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();
    }
}

客户端场景类:

public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer("张三");
        IGamePlayer proxy = new GamePlayerProxy(player);

        proxy.login("张三", "123456");
        proxy.killBoss();
        proxy.upgrade();
    }
}

2.代理模式

代理模式是一个使用率非常高的模式,其定义如下:为其他对象提供一种代理以控制对这个对象的访问。代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了代理模式,而且在日常应用中,代理模式可以提供非常好的访问控制。我们熟知的Spring AOP就是一个非常典型的动态代理。

2.1 代理模式的优点

  • 职责清晰:真实的角色就是实现实际的业务逻辑,不用关系其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
  • 高扩展性:具体主题角色是随时都会发生变化的,只要它实现了接口,无论它如何变化,那我们的代理类完全就可以在不做任何修改的情况下使用。
  • 智能化

2.2 代理模式的扩展

2.2.1 普通代理

普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。这样的解释还是比较复杂,我们还是用实例来讲解。

首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色。假设,玩家不再玩游戏,完全委托给代理,我们修改类图如下:

改动很小,仅仅是修改了两个实现类的构造函数,GamePlayer的构造函数增加了gamePlayer参数,而代理角色则只要传入代理者名字即可。实现代码如下:

public class GamePlayer implements IGamePlayer {
    private String name;

    public GamePlayer(IGamePlayer gamePlayer, String name) throws Exception {
        if (!gamePlayer.getClass().equals(GamePlayerProxy.class)) {
            throw new Exception("不能创建真实角色,必须使用代理");
        }
        this.name = name;
    }

    @Override
    public void login(String user, String password) {
        System.out.println(user + "登录成功");
    }

    @Override
    public void killBoss() {
        System.out.println(this.name + "在打怪");
    }

    @Override
    public void upgrade() {
        System.out.println(this.name + "升了一级");
    }
}

代理类实现如下: 

public class GamePlayerProxy implements IGamePlayer {
    private IGamePlayer gamePlayer = null;

    public GamePlayerProxy(String name) {
        try {
            gamePlayer = new GamePlayer(this, name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @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();
    }
}

场景类:

public class Client {
    public static void main(String[] args) {
        IGamePlayer proxy = new GamePlayerProxy("张三");

        proxy.login("张三", "123456");
        proxy.killBoss();
        proxy.upgrade();
    }
}


张三登录成功
张三在打怪
张三升了一级

在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的角色想怎么改就怎么改,对高层模块没有任何影响。该模式非常适合对扩展性要求较高的场合。

2.2.2 强制代理

一般的思维都是通过代理找到真实的角色,而强制代理要求必须通过真实角色查找到代理角色,然后由代理角色来访问真实角色。实现类图如下;

强制代理的接口类:

public interface IGamePlayer {
    /**
     * 游戏登录
     *
     * @param name
     * @param password
     */
    void login(String user, String password);

    /**
     * 杀怪
     */
    void killBoss();

    /**
     * 升级
     */
    void upgrade();

    /**
     * 每个角色都能找到自己的代理
     *
     * @return
     */
    IGamePlayer getProxy();
}

仅仅增加了一个getProxy方法,指定要访问自己必须通过哪个代理。强制代理的真实角色代码如下:

public class GamePlayer implements IGamePlayer {
    private String name;
    private IGamePlayer proxy;

    public GamePlayer(String name) {
        this.name = name;
    }

    /**
     * 找到自己的代理
     *
     * @return
     */
    @Override
    public IGamePlayer getProxy() {
        this.proxy = new GamePlayerProxy(this);
        return this.proxy;
    }

    @Override
    public void login(String user, String password) {
        if (isProxy()) {
            System.out.println(user + "登录成功");
        }
    }

    @Override
    public void killBoss() {
        if (isProxy()) {
            System.out.println(this.name + "在打怪");
        }
    }

    @Override
    public void upgrade() {
        if (isProxy()) {
            System.out.println(this.name + "升了一级");
        }
    }

    /**
     * 校验是否是代理访问
     *
     * @return
     */
    private boolean isProxy() {
        return this.proxy != null;
    }
}

增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问。强制代理的代理类:

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();
    }

    /**
     * 代理的代理暂时还没有,就是自己
     *
     * @return
     */
    @Override
    public IGamePlayer getProxy() {
        return this;
    }
}

代理的角色也可以再次被代理,这里我们没有继续延续下去,查找代理的方法就返回自己的实例。强制代理的实现场景类:

public class Client {
    public static void main(String[] args) {
        //定义一个游戏的角色
        IGamePlayer gamePlayer = new GamePlayer("张三");
        //获得指定的代理
        IGamePlayer proxy = gamePlayer.getProxy();
        proxy.login("张三", "123456");
        proxy.killBoss();
        proxy.upgrade();
    }
}

强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理由真实角色来控制。

一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标方法进行拦截和过滤。代理类可以为真实角色预处理消息,过滤消息,消息转发,事后处理消息等功能。

3.动态代理

动态代理是在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象。现在有一个非常流行的名称叫做面向切面编程(AOP),其核心就是采用了动态代理机制。动态代理类图修改如下:

在类图中增加了一个InvocationHandler接口和GamePlayH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。相关代码定义如下:

package com.martin.learn.patterns.chapter12;

/**
 * @author: martin
 * @date: 2019/9/26 22:27
 * @description:
 */
public interface IGamePlayer {
    /**
     * 游戏登录
     *
     * @param user
     * @param password
     */
    void login(String user, String password);

    /**
     * 杀怪
     */
    void killBoss();

    /**
     * 升级
     */
    void upgrade();
}


package com.martin.learn.patterns.chapter12;

/**
 * @author: martin
 * @date: 2019/9/26 22:29
 * @description:
 */
public class GamePlayer implements IGamePlayer {
    private String name;
    private IGamePlayer proxy;

    public GamePlayer(String name) {
        this.name = name;
    }

    @Override
    public void login(String user, String password) {
        System.out.println(user + "登录成功");
    }

    @Override
    public void killBoss() {
        System.out.println(this.name + "在打怪");
    }

    @Override
    public void upgrade() {
        System.out.println(this.name + "升了一级");
    }
}

代理类:

package com.martin.learn.patterns.chapter12;

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

/**
 * @author: martin
 * @date: 2019/10/13 16:31
 * @description:
 */
public class GamePlayIH implements InvocationHandler {
    /**
     * 被代理者
     */
    private Class clazz;
    /**
     * 被代理的实例
     */
    private Object object;

    //我要代理谁
    public GamePlayIH(Object object) {
        this.object = object;
    }

    //调用被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print("代理操作:");
        return method.invoke(this.object, args);
    }
}

实现的场景类Client:

package com.martin.learn.patterns.chapter12;

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

/**
 * @author: martin
 * @date: 2019/9/28 20:31
 * @description:
 */
public class Client {
    public static void main(String[] args) {
        //定义一个游戏的玩家
        IGamePlayer gamePlayer = new GamePlayer("张三");
        //定义一个Handler
        InvocationHandler handler = new GamePlayIH(gamePlayer);
        //获得类的ClassLoader
        ClassLoader classLoader = gamePlayer.getClass().getClassLoader();
        //动态产生一个代理者
        IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(classLoader, new Class[]{IGamePlayer.class}, handler);
        proxy.login("张三", "123456");
        proxy.killBoss();
        proxy.upgrade();
    }
}

代码执行结果输出如下:

代理操作:张三登录成功
代理操作:张三在打怪
代理操作:张三升了一级

其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。如果我们想让游戏登录之后发一个消息给我们,防止盗号,该如何修改?我们可以直接修改动态代理类:

package com.martin.learn.patterns.chapter12;

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

/**
 * @author: martin
 * @date: 2019/10/13 16:31
 * @description:
 */
public class GamePlayIH implements InvocationHandler {
    /**
     * 被代理者
     */
    private Class clazz;
    /**
     * 被代理的实例
     */
    private Object object;

    //我要代理谁
    public GamePlayIH(Object object) {
        this.object = object;
    }

    //调用被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print("代理操作:");
        Object result = method.invoke(this.object, args);
        if (method.getName().equalsIgnoreCase("login")) {
            System.out.println("有人在用我的账号登录!");
        }
        return result;
    }
}

当有人用我的账号登录时,就发送一个消息,然后看看自己的账号是不是被人盗取,非常有用的方法,这就是AOP编程。AOP编程没有使用什么新的技术,但是它对我们的设计、编码都有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方法切入进去。

4.最佳实战

代理模式应用的非常广泛,比如Spring AOP和AspectJ这样非常优秀的框架都是使用的代理模式。在学习这些优秀的框架时,需要弄清楚切面、切入点、通知和织入这些名词。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值