设计模式之代理模式

本文深入介绍了代理模式,包括其定义、优点、在游戏场景中的应用,以及普通代理和强制代理的拓展。此外,还探讨了动态代理的实现,特别是如何通过JDK的动态代理实现前置和后置通知。代理模式通过提供额外的控制层,增强了系统的扩展性和职责划分的清晰性。

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


1.什么是代理模式

代理模式是一个使用率非常高的模式,其定义如下:

  • 为其他对象提供一种代理以控制对这个对象的访问

代理模式也叫委托模式,它是一项基本设计技巧。

代理模式主要分为3个角色:

  • Subject抽象主题角色 抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求
  • RealSubject具体主题角色 也叫做被委托角色,被代理角色,是业务逻辑的具体执行者
  • Proxy代理主题角色 也叫做委托类,代理类。它负责对真实角色的应用,把所有抽象主题定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
抽象主题类
public interface Subject {
    public void request();
}
真实主题类
public class RealSubject implements Subject {
    @Override
    public void request() {
        
    }
}
代理类
public class Proxy implements Subject {
    
    private Subject subject = null;

    public Proxy() {
        this.subject = new Proxy();
    }

    public Proxy(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void request() {
        this.before();
        this.subject.request();
        this.after();
    }
    
    private void before(){
        
    }
    
    private void after(){
        
    }
}

2.代理模式的优点

  • 职责清晰

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

  • 高扩展性

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

3.代理模式的应用

3.1 打游戏

先来看一个打游戏的例子,定义游戏者接口,游戏者以及场景类。

游戏者接口
public interface IGamePlayer {
    public void login(String user,String password);

    public void killBoss();

    public void upgrade();
}
游戏者
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(this.name+"在登录");
    }

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

    @Override
    public void upgrade() {
        System.out.println(this.name+"在升级");
    }
}
场景类
public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer("张三");
        player.login("zhangsan","password");
        player.killBoss();
        player.upgrade();
    }
}
运行结果如下:

张三在登录
张三在打怪
张三在升级

3.2 代练

假如说我们工作很忙没有时间玩游戏,但是游戏又需要每天完成任务,这个时候我们就需要一个代练,这个代练就如同代理类。

代练者
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("zhangsan","password");
        proxy.killBoss();
        proxy.upgrade();
    }
}
运行结果如下:

张三在登录
张三在打怪
张三在升级

经过代练,游戏已经在升级打怪,并且不需要自己动手,代练会帮忙搞定一切。

4.代理模式的拓展

在网络上代理服务器设置分为透明代理和普通代理,是什么意思呢?透明代理就是用户不用设置代理服务器地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不用知道它存在的;普通代理则是需要用户自己设置代理服务器的IP地址,用户必须知道代理的存在。

4.1普通代理

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

对于打游戏这个例子而言,也就是场景类不能再直接new一个GamePlayer对象了,它必须由GampePlayerProxy来进行模拟场景。

因此,GamePlayer修改为:

游戏者
public class GamePlayer implements IGamePlayer {

    private String name = "";

    //构造函数限制谁能创建对象,并同时传递姓名
    //在构造函数中,传递进来一个IGamePlayer对象,检查谁能创建真实的角色,当然还可以有其他的限制,比如类名必须为Proxy类等,可以根据实际情况进行扩展
    public GamePlayer(IGamePlayer iGamePlayer,String name) throws Exception {
        if(iGamePlayer == null){
            throw new Exception("不能创建真实角色");
        }else {
            this.name = name;
        }
    }

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

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

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

GamePlayerProxy修改为:

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

仅仅修改了构造函数,传递进来一个代理者名称,即可进行代理,在这种改造下,系统更加简洁了,调用者只知道代理存在就可以,不用知道代理了谁。

Client修改为:

场景类
public class Client {
    public static void main(String[] args) {
        IGamePlayer proxy = new GamePlayerProxy("张三");
        proxy.login("zhangsan","password");
        proxy.killBoss();
        proxy.upgrade();
    }
}
运行结果如下:

张三在登录
张三在打怪
张三在升级
  • 运行结果完全相同。在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。

4.2 强制代理

强制代理在设计模式中比较另类,为什么这么说呢?一般的思维都是通过代理找到真实的角色,但是强制代理却是要“强制”,你必须通过真实角色查找到代理角色,否则你不能访问。

游戏者接口

在接口上增加了一个getProxy方法,真实角色GamePlayer可以指定一个自己的代理,除了代理外谁都不能访问。

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(IGamePlayer iGamePlayer,String name) throws Exception {
        if(iGamePlayer == null){
            throw new Exception("不能创建真实角色");
        }else {
            this.name = name;
        }
    }

    @Override
    public IGamePlayer getProxy() {
        this.proxy = new GamePlayerProxy(this.name);
        return this.proxy;
    }

    public void killBoss(){
        if(this.isProxy()){
            System.out.println(this.name+"在打怪!");
        }else{
            System.out.println("请使用指定的代理访问");
        }
    }

    @Override
    public void upgrade() {
        if(this.isProxy()){
            System.out.println(this.name+"又升了一级!");
        }else{
            System.out.println("请使用指定的代理访问");
        }
    }

    //进游戏之前你肯定要登录吧,这是一个必要条件
    public void login(String user,String password){
        if(this.isProxy()){
            System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
        }else{
            System.out.println("请使用指定的代理访问");
        }
    }

    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;
    }
}
场景类
public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer("张三");
        IGamePlayer proxy = player.getProxy();
        proxy.login("zhansan","password");
        proxy.killBoss();
        proxy.upgrade();
    }
}

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

5.动态代理

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

相对来说,自己写代理类的方式就是静态代理。

现在有一个非常流行的名称叫做面向横切面编程,也就是 AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。

注意要实现动态代理的首要条件是:被代理类必须实现一个接口

现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。

在上面的例子中增加了一个InvocationHanlder接口GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHanlder是JDK提供的动态代理接口,对被代理类的方法进行代理。

动态代理类
public class GamePlayIH implements InvocationHandler {

    //被代理者
    Class cls=null;
    //被代理的实例
    Object obj=null;
    //我要代理谁
    public GamePlayIH(Object obj){
        this.obj= obj;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(this.obj,args);
        return result;
    }
}

其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。我们来详细讲解一下InvocationHanlder接口,动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”.

那动态代理怎么才能实现被代理接口中的方法呢?默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。

动态代理的场景类
public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer("张三");

        InvocationHandler handler = new GamePlayIH(player);
        //获得类的class loader
        ClassLoader cl = player.getClass().getClassLoader();
        //动态产生一个代理者
        IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class},handler);

        proxy.login("zhangsan","password");
        proxy.killBoss();
        proxy.upgrade();
    }
}

我们还是让代练者帮我们打游戏,但是我们既没有创建代理类,也没有实现IGamePlayer接口,这就是动态代理。

如果想让游戏登录后发一个信息给我们,防止账号被人盗用,也可以使用动态代理,只需要修改 GamePlayIH

动态代理类
public class GamePlayIH implements InvocationHandler {

    //被代理者
    Class cls=null;
    //被代理的实例
    Object obj=null;
    //我要代理谁
    public GamePlayIH(Object obj){
        this.obj= obj;
    }

    @Override
    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;
    }
}

5.1 实现前置后置通知

通知接口
public interface IAdvice {
    public void exec();
}
前置通知类
public class BeforeAdvice implements IAdvice{
    @Override
    public void exec() {
        System.out.println("我是前置通知,我被执行了");
    }
}
后置通知类
public class AfterAdvice implements IAdvice{
    @Override
    public void exec() {
        System.out.println("我是后置通知,我被执行了");
    }
}
public interface Subject {
    public void doSomething(String str);
}
public class RealSubject implements Subject {
    @Override
    public void doSomething(String str) {
        System.out.println("do something!--->"+str);
    }
}
public class MyInvocationHandler implements InvocationHandler {

    private Object target = null;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前置通知
        (new BeforeAdvice()).exec();
        //执行被代理的方法
        Object invoke = method.invoke(this.target, args);
        //后置通知
        (new AfterAdvice()).exec();

        return invoke;
    }
}
public class DynamicProxy<T>{
    public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
        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");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值