23种设计模式探索之代理模式
Posted 卓诺不迷路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了23种设计模式探索之代理模式相关的知识,希望对你有一定的参考价值。
前段时间加班赶项目,忙的飞起,好在如期出版本了,这些天的辛苦也算没白费。很快又要开始忙起来了,趁着这个间隙,给大家分享一下我对设计模式的理解。今天就从使用最广的代理模式开始。
什么是代理模式
代理模式也称委托模式,是结构型模式的一种。简单讲就是,给某一对象提供一个代理对象,由代理对象控制外部对原对象的访问,并决定原对象的动作。
举个例子。我是一个程序员,我在给客户在做一个项目,客户提需求,我开发需求。但是呢,客户提的需求可能是模糊的、不合理的。我作为一个程序员是很忙的,不想、也没时间跟客户BB,怎么办呢?我可以委托项目经理去跟客户对接,梳理、过滤客户需求,确定那些需求需要开发、哪些需求不用开发。而我,只管接受项目经理给我的开发任务就行了。
上面这个例子中,项目经理就相当于代理类(也称委托类),我相当于被代理类(也称被委托类),我们的共同目的是做好这个项目(抽象主题)。
代理模式分类
通常我们根据代理类class文件被创建的时间,将代理模式分为静态代理、动态代理两大类。
静态代理,在程序编译的时候,代理类.class文件就已经被创建了。也就是说,在代码块执行之前,就知道是代理谁了。
动态代理,在代码块运行的时候,通过反射来动态的生成代理类.class文件及代理类的对象,并确定代理谁。
下面讲讲两种代理模式的具体实现,仍然以项目经理-程序员为例。
静态代理
在这个例子中,项目经理(代理类)和程序员(被代理类)有着共同的目的(抽象主题)——做项目。我们可以抽象出一个共同的业务接口IDoProject,代理类ProjectManagerLuffy和被代理类ProgrammerZoro分别实现这个业务接口。
不多BB,直接贴代码。
1,抽象主题类IDoProject.java
public interface IDoProject {
void work();
}
2,被代理类(程序员卓诺)ProgrammerZoro.java
public class ProgrammerZoro implements IDoProject{
public void work() {
Log.d("fxp", "程序员Zoro开发需求...");
}
}
3,代理类(项目经理路飞)ProjectManagerLuffy.java
public class ProjectManagerLuffy implements IDoProject{
private ProgrammerZoro zoro;
public ProjectManagerLuffy(ProgrammerZoro zoro){
this.zoro = zoro;
}
public void work() {
// 整理需求
Log.d("fxp", "项目经理Luffy整理需求...");
// 评估需求是否合理
boolean isReasonable = (new Random()).nextBoolean();
Log.d("fxp", "项目经理Luffy评估需求是否合理...");
if (isReasonable){
// 需求合理-提交给程序员
Log.d("fxp", "需求评估结果为合理,项目经理Luffy将需求提交给程序员Zoro...");
zoro.work();
} else {
// 需求不合理-驳回
Log.d("fxp", "需求评估结果为不合理,项目经理Luffy不将需求提交给程序员Zoro");
}
}
}
4,客户端
private void commitNeedsToLuffy(){
Log.d("fxp", "客户将需求提交给项目经理Luffy...");
ProjectManagerLuffy luffy = new ProjectManagerLuffy(zoro);
luffy.work();
}
运行结果:
或
静态代理中,代理类和被代理类关系太密切了。按照静态代理的实现方式,如果有多个项目经理,我们就得创建多个代理类ProjectManager1、ProjectManager2...。N个项目经理就得创建N个类,这显然是不合适的。而且,所有的代理类都需要实现与被代理类的共同业务接口,一旦共同业务接口增加方法,所有的代理类都得维护,这显然也是不合适的。
使用动态代理可以解决这些问题。
动态代理
动态代理又分JDK代理(也称接口代理)和CGLIB代理,今天我们只聊JDK代理。JDK代理特点:调用Proxy类的newProxyInstance方法,通过反射动态的生成代理对象。
下面我们一起看看Proxy类的newProxyInstance方法源码。当然,我已经删除了废弃部分,并手动添加了中文注释讲解:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
// 克隆一份业务接口
final Class<?>[] intfs = interfaces.clone();
// 调用getProxyClass0()方法,生成一个实现了业务接口的class字节码对象cl(将类加载器和克隆的业务接口作为参数)
Class<?> cl = getProxyClass0(loader, intfs);
try {
// 获取cl对象的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
// BEGIN android-changed: Excluded AccessController.doPrivileged call.
cons.setAccessible(true);
// END Android-removed: Excluded AccessController.doPrivileged call.
}
// 通过构造方法,以反射的方式返回一个动态代理对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
这是个静态方法,有三个参数:
ClassLoader:类加载器,用来加载claas文件,和加载被代理类的类加载器是一致的
Class<?>[]:共同的业务接口
InvocationHandler:业务处理回调,被代理类真实业务处理的地方
我们可以看到,第二个参数是数组!也就是说可以同时实现多个业务接口,并在回调中处理多个业务接口的业务!如果你看Retrofit的源码,你会发现Retrofit就是这么做的。newProxyInstance方法的返回值是一个Object,可以强转成一个接口对象。
下面开始实践。直接上代码:
private void commitNeedsToCompany(){
Log.d("fxp", "客户提交需求...");
Log.d("fxp", "指定项目经理Nami...");
// 动态生成代理对象,并强转为业务接口对象
IDoProject nami = (IDoProject) Proxy.newProxyInstance(zoro.getClass().getClassLoader(), new Class[]{IDoProject.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 整理需求
Log.d("fxp", "项目经理Nami整理需求...");
// 评估需求是否合理
boolean isReasonable = (new Random()).nextBoolean();
Log.d("fxp", "项目经理Nami评估需求是否合理...");
if (isReasonable){
// 需求合理-提交给程序员
Log.d("fxp", "需求评估结果为合理,项目经理Nami将需求提交给程序员Zoro...");
zoro.work();
} else {
// 需求不合理-驳回
Log.d("fxp", "需求评估结果为不合理,项目经理Nami不将需求提交给程序员Zoro");
}
return null;
}
});
nami.work();
}
运行结果:
或
JDK动态代理是典型的面向接口编程,依赖于抽象而不依赖于具体,遵循依赖倒转原则。
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,可以使用CGLIB实现。至于CGLIB代理,在这里就不做叙述了。感兴趣的朋友可以自行搜索相关资料。
如果根据代理模式的应用场景分类,代理模式又可以分为以下几类。
1,远程代理
2,虚拟代理
如果需要创建一个消耗较大的对象,先创建一个消耗较小的对象来表示,真实对象只在需要时才被真实创建。比如Android中的AMS功能非常强大,但非常消耗资源,所以通过AMSProxy给App提供Activity管理部分的功能。
3,安全代理
用来控制对真实对象的访问权限。
静态代理 VS 动态代理
静态代理
优点:
1,业务类只需要关注业务逻辑本身;
缺点:
1,可能需要为同一业务接口创建多个代理类,重复编码;
2,业务接口一旦发生改变,所有代理类和被代理类都需要修改;
3,一种代理类只服务于一种业务接口;(比如在上面静态代理示例中,ProjectManagerLuffy只代理ProgrammerZoro的IDoProject业务。如果还需要代理其他比如IPlayGames业务,就需要重新创建代理类了。)
JDK动态代理
优点:
1,动态创建代理对象;
2,可以同时代理多个业务接口;
3,可以在InvocationHandler.invoke()中灵活处理接口方法;
缺点:
1,只能代理实现了业务接口的被代理类;
2,性能有损耗;
代理模式 VS 装饰模式
看完上面静态代理的实践示例,可能有的同学就有疑问了,这不是装饰模式吗?有这个疑问是很好的,代理模式和装饰模式确实很容易混淆。
不同设计模式之间区别,除了代码层面的实现方式,还有设计的目的。
代理模式和装饰模式都属于结构型模式,在代码实现上确实没什么区别,但他们的目的是不一样的。在代理模式中,代理类对被代理类是有控制权的,可以决定被代理类做不做什么事情。在装饰模式中,装饰类对被装饰类是没有控制权的,装饰类通常只对其进行装饰,增强被装饰类的功能。
还是以项目经理-程序员为例。如果项目经理过滤项目需求,决定哪些需求需要开发,哪些需求不需要开发,那么这就是代理模式了。如果项目经理对需求不做任何过滤,都交给程序员,提高程序员开发设备配置,或者提供程序员鼓励师,那么这就是装饰模式了。
今天的分享就到此为止了,欢迎各位大佬批评指正。欢迎留言或私信交流讨论!
欢迎投稿欢迎投稿欢迎投稿!重要的事情说三遍!
以上是关于23种设计模式探索之代理模式的主要内容,如果未能解决你的问题,请参考以下文章