前言
代理模式的目的是提供一个代理来控制对一个对象的访问。那么,我们为什么需要控制对一个对象的访问呢?或者说控制对一个对象的访问有什么好处呢?在日常工作中,大家应该会遇到过以下这些问题:一个对象的加载时间太长或者太耗费资源,因此我们需要在必要的时候再加载它;一个对象运行在其他计算机上,而我们需要调用该对象的某些方法;我们可能还需要拦截一个对象的某些方法,并在该方法前后加入一些业务逻辑……以上问题就非常适合用代理模式来解决,因为我们可以通过代理对象控制对真实对象的访问,这样可以做到在必要的时候才加载真实对象或者在真实对象的方法上加入一些业务逻辑。
背景
假如我们有一个用户登录的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方法。这样一来,当目标接口变化的时候,动态代理类就不需要根据接口的变化而变化了。
如果觉得这篇文章对你有帮助,可以扫描下方二维码,关注本人公众号,获得更多优质文章推送。