聊聊设计模式之代理模式

前言

代理模式的目的是提供一个代理来控制对一个对象的访问。那么,我们为什么需要控制对一个对象的访问呢?或者说控制对一个对象的访问有什么好处呢?在日常工作中,大家应该会遇到过以下这些问题:一个对象的加载时间太长或者太耗费资源,因此我们需要在必要的时候再加载它;一个对象运行在其他计算机上,而我们需要调用该对象的某些方法;我们可能还需要拦截一个对象的某些方法,并在该方法前后加入一些业务逻辑……以上问题就非常适合用代理模式来解决,因为我们可以通过代理对象控制对真实对象的访问,这样可以做到在必要的时候才加载真实对象或者在真实对象的方法上加入一些业务逻辑。

背景

假如我们有一个用户登录的LoginService接口,其有一个login方法,用来实现用户登录功能。LoginService接口有一个实现类:LoginServiceImpl,其实现了login方法,提供了基本的用户登录功能。在系统稳定运行了一段时间之后,我们发现有一些用户经常发一些违规内容,因此我们需要限制此类用户的登录。最简单的方法就是在LoginServiceImpl的login方法里面修改原先的代码,加入对违规用户登录的检查,但是这样做的问题是容易出现bug,假如说原先的login方法已经很复杂了,这个时候即使只修改几行代码都可以会出现问题。再者,软件开发的一个基本原则是“对扩展开放,对修改关闭”,也就是说我们应该尽可能地通过扩展来适应功能的变化,而不是通过修改已有的代码来满足新需求。所以,在原来的LoginServiceImpl上对login方法进行修改的方案虽然简单,却有诸多缺点,因此我们接下来介绍通过代理模式来实现对原来的登录功能进行扩展。

代理模式的结构

首先我们先讲一下代理模式的类图结构。

  • RealSubject是真实对象
  • Proxy是代理对象,其持有真实对象的引用
  • Subject是真实对象跟代理对象的共有接口

由于代理对象与真实对象都实现了相同的接口,所以可以在一定程度上“代替”真实对象,充当真实对象的“代理”。又因为代理对象持有真实对象的引用,所以可以在必要的时候对真实对象进行调用。

使用代理模式

接下来,我们用代理模式来实现对上述用户登录功能的扩展。

我们首先编写LoginService接口:

public interface LoginService {    
    void login(String userId);
}

其只有一个抽象方法login。

接着我们编写LoginServiceImpl类以实现最基本的登录功能:

public class LoginServiceImpl implements LoginService{

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

在LoginServiceImpl类,我们默认让所有用户都能正常登录。

然后我们编写代理类,以限制违规用户的登录:

public class LoginServiceProxy implements LoginService{

    private LoginService realLoginService;    

    public LoginServiceProxy(LoginService realLoginService) {
        this.realLoginService = realLoginService;
    }    

    @Override
    public void login(String userId) {        
        if(isIllegalUser(userId)){
            System.out.println("你的账号涉嫌违规,禁止登录。");  
            return;
        }
        realLoginService.login(userId);
    }    

     private boolean isIllegalUser(String userId){
        if(userId.equalsIgnoreCase("user1")){
            return true;
        }        
        return false;
    }

}

代理类LoginServiceProxy同样也是实现了LoginService接口,并持有真实对象的引用。在代理类的login方法中,我们对用户是否违规进行了判断,如果用户是违规用户,则禁止登录,否则就调用真实对象的login方法走正常的登录流程。

最后,我们写一个客户端测试一下:

public class Client {
    public static void main(String[] args) {
        LoginService realLoginService=new LoginServiceImpl();
        LoginService loginServiceProxy=new LoginServiceProxy(realLoginService);
        loginServiceProxy.login("user2");
        loginServiceProxy.login("user1");
    }
}

结果输出如下:

可以看到,我们已经在原来的登录功能之上增加了对违规用户的过滤功能。不仅如此,我们并没有改动原来的LoginServiceImpl类中的一行代码,假如有一天我们不需要违规用户的过滤功能了,则客户端直接使用LoginServiceImpl类的登录功能即可,不需要做其他改动。

代理模式的改进

上述代理模式通常称为静态代理,其有一个弊端就是,如果LoginService发生变化,比如增加了新的方法,那么所有目标类跟代理类都需要做变动,不是很灵活。实际上在Java中还有另外一种代理模式,称为动态代理。其优点是无论目标接口定义了多少方法,动态代理类始终只有一个invoke方法。这样一来,当目标接口变化的时候,动态代理类就不需要根据接口的变化而变化了。由于篇幅所限,后续有时间再对动态代理进行详细介绍。

原文发布于微信公众号 - 编程沉思录(code-thinker)

原文发表时间:2018-02-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序你好

C# API中的模型和它们的接口设计

962
来自专栏腾讯Bugly的专栏

【团队分享】刀锋铁骑:常见Android Native崩溃及错误原因

王竞原,负责网游刀锋铁骑项目,高级开发工程师,使用C++已有10年,非常喜欢C++,特别是C++11。希望能与广大的C++爱好者多交流。 一、什么是Androi...

5073
来自专栏desperate633

浅谈servlet的生命周期servlet的生命周期详解servlet生命周期中三大重要的时刻

servlet从被加载到销毁经历了多个阶段,其中需要我们十分了解每个阶段的意义作用,才能更好地编写相关的servlet程序。

932
来自专栏积累沉淀

Python快速学习第十二天--生成器和协程

yield指令,可以暂停一个函数并返回中间结果。使用该指令的函数将保存执行环境,并且在必要时恢复。 生成器比迭代器更加强大也更加复杂,需要花点功夫好好理解贯...

8275
来自专栏java一日一条

Java线程面试题 Top 50

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚...

1212
来自专栏IT大咖说

基于PG数据库插件的SQL规范审核工具

内容来源:2017 年 10 月 21 日,平安科技数据库架构师陈刚在“PostgreSQL 2017中国技术大会”进行《基于PG数据库插件的SQL规范审核工具...

1932
来自专栏游戏开发那些事

【Linux下进程机制】从一道面试题谈linux下fork的运行机制

      已知从这个程序执行到这个程序的所有进程结束这个时间段内,没有其它新进程执行。

1992
来自专栏程序员宝库

JAVA 中异常处理的最佳实践

前言 异常处理的问题之一是知道何时以及如何去使用它。我会讨论一些异常处理的最佳实践,也会总结最近在异常处理上的一些争论。 作为程序员,我们想要写高质量的能够解决...

3168
来自专栏刘望舒

设计模式(十五)状态模式

前言 建议在阅读本文前先阅读设计模式(十一)策略模式这篇文章,虽说状态模式和策略模式的结构几乎是相同的,但是它们所解决的问题是不同的,读完这两篇文章你就会有了答...

2046
来自专栏JAVA烂猪皮

BAT面试常的问题和最佳答案

客户端发出http请求,web服务器将请求转发到servlet容器,servlet容器解析url并根据web.xml找到相对应的servlet,并将reques...

1022

扫码关注云+社区

领取腾讯云代金券