设计模式-代理模式
代理模式的概念
代理模式(PRoxy pattern)是一种结构型的设计模式,代理模式在程序开发中的运用非常广泛。简单地描述代理模式就是:为其他对象(被代理对象)提供一种代理以控制对原有对象的操作。实际的行为是由被代理对象完成的。 代理模式可以分为两部分,静态代理 和 动态代理,它们的区别将在下面详细介绍。
角色介绍:
Suject: 抽象主题类 该类的主要职责是申明真是主题与代理的共同接口方法,该类既可以是个抽象类也可以是个接口(具有抽象方法)。 RealSubject: 真实主题类 该类也称为委托类或者被代理类,改类定义了代理所表示的真是对象(也就是实现了抽象方法),由其执行具体的业务逻辑。 ProxySubject:代理类 这个类的对象持有一个对真实主题的引用,在这个类所实现的接口方法中调用真实主题类中相应的方法执行,这样就实现了代理的目的。 Client:客户类 也就是使用代理类的类型,客户类通过代理类间接地调用了真实主题类中定义的方法。
代理模式的实现
简单的例子
针对,上方的角色介绍,举一个简单的例子:在现实的世界中,打公司一般有,原告 和原告的代理律师这样两个角色,他们要做的事情是 辩护。于是,我们可以实现以下几个类。 抽象主题类:interface IDefender,这个接口中有个 抽象方法 abstract public void defend(); 表示辩护这个行为。
public interface IDefender { abstract public void defend();//辩护行为}
123123真实主题类: class Accuser,实现IDefender这个接口,具有具体的逻辑行为
public class Accuser implements IDefender { @Override public void defend() { System.out.println("被告严重侵犯了公民的人身自由权...哔哩哔哩"); }}
12345671234567代理类:AccuserProxy 类,原告请了个代理律师帮助它打官司,也就是AccuserProxy,由代理律师控制原告的表现
public class AccuserProxy implements IDefender { //持有被代理类的引用 private IDefender iDefend; public AccuserProxy(IDefender iDefend) { this.iDefend = iDefend; } @Override public void defend() { beforeDefend(); // 被代理类的行为 iDefend.defend(); afterDefend(); } //修饰的方法 private void beforeDefend(){ System.out.print("我是原告律师,以下是我方辩词"); } //修饰的方法 private void afterDefend(){ System.out.print("辩护完毕"); }}
123456789101112131415161718192021222324123456789101112131415161718192021222324客户端:Client类,使用代理类的角色
public class Client { public static void main(String[] args) { Accuser accuser = new Accuser(); accuser.defend();//此时输出 被告严重侵犯了公民的人身自由权...哔哩哔哩 AccuserProxy accuserProxy = new AccuserProxy(accuser); accuserProxy.defend(); // 输出:我是原告律师,以下是我方辩词,被告严重侵犯了公民的人身自由权...哔哩哔哩,辩护完毕 ///////////////////////////////////////////////////// // 因为,我们的代理类和被代理类 实现的是同一接口,如果将引用类型 写成IDfender, // 那么在调用 defend(),方法的使用 客户完全感觉不到被代理类的存在,当然因为我们这里的 // 被代理类是通过构造函数传进去的,软件开发中,有时候直接在被代理类中实例化代理类,这样使用起来就更完美了。 IDefender accuser2 = new Accuser(); IDefender accuser2Proxy = new AccuserProxy(accuser2); accuser2.defend(); }}
1234567891011121314151617181912345678910111213141516171819 代理模式的运用符合开闭原则的定义,软件中的对象(类、模块、函数)应该对于拓展是开发的,对于修改是封闭的。我们不需要修改被代理类 (Accuser),使用代理模式就可以实现对原有功能的加强。以上代码只是一个简答的运用场景,现实开发中代理模式的运用非常广泛,它可以解决多方面的问题。
Androd 开发中的运用例子
Android API的版本迭代很快,在每次的版本更新中 通常会加强一些原有的功能,对原有的类会增加新的接口,而每个版本能够调用的API 可能会都不同,比如 状态栏 Notification 。
Notfification可以分为4类,一类是正常视图,也就是我们通常在状态栏看到的 高度为 64dp 的长条状通知视图;一类是在 API16 中引入的以 Style 方式展示的 MediaStyle、InboxStyle、BigTextStyle 和 BigPictureStyle 四种Notification 风格样式;一类也是在 API16 中引入的可以将通知视图显示为256dp 高度大视图的 bingContentView;最后一类是在 L 中引入的 headsUpContentVIew
现在,我们的 App需要根据不同的版本实例化不同 Notification 显示不同的视图,在高版本显示的视图当然就更丰富,低版本更单调。如果你直接在 Activity 中判断版本,加上一坨的 switch 或者 if else 语句当然也是可以的。但是,我们现在来看一下 运用设计模式的思想,如何将代码抽离,对于客户端来说(Activity or Fragment),不应该关心这些细节。 以下的代码来自《Android源码设计模式解析与实战》一书。
抽象主题类: Notify类, Notify抽象类 声明了 NotificationManager 和NotificatoinCompat.Builder 2个成员变量来处理和通知的一些逻辑。在构造函数中初始化所有子类都会调用的逻辑方法。
public abstract class Notify { protected Context context; protected NotificationManager nm; protected NotificationCompat.Builder builder; public Notify(Context context){ this.context = context; nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); builder = new NotificationCompat.Builder(context); builder.setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(PendingIntent.getActivities( context, 0, new Intent[]{new Intent(context, NotifyActivity.class)}, PendingIntent.FLAG_UPDATE_CURRENT)); } /** * 发送一条通知 */ public abstract void send(); /** * 取消一条通知 */ public abstract void cancel();}
1234567891011121314151617181920212223242512345678910111213141516171819202122232425真实主题类:NotifyNormal ,继承抽象主题类,简单重写抽象方法
public class NotifyNormal extends Notify{ public NotifyNormal(Context context) { super(context); } @Override public void send() { Notification n = builder.build(); n.contentView = new RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal); nm.notify(0,n); } @Override public void cancel() { nm.cancel(0); }}
123456789101112131415161718123456789101112131415161718真实主题类 : 与NormalNotify的不同在于 还实现了bigContentView 的初始化,这是在 API 16以上才能调用的
public class NotifyBig extends Notify{ public NotifyBig(Context context) { super(context); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void send() { Notification n = builder.build(); n.contentView = new RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal); n.bigContentView = new RemoteViews(context.getPackageName(),R.layout.remote_notify_proxy_big); nm.notify(0,n); } @Override public void cancel() { nm.cancel(0); }}
123456789101112131415161718192021123456789101112131415161718192021 源码中还有个 NotifyHeadUp 类,它的唯一区别就是 在bigContentVie 的基础上还能实现 Notification 的 headsUpContentView ,这个 View 是在 API 20以上 才能使用的,当我们的 App 以全屏的方式展开的时候如果收到了通知,这个 View 就会浮动展示于屏幕顶部。
代理类: NotifyProxy ,持有被代理类对象,在这个示例中,我们根据不同的运行时环境 API 实例化不同的 被代理类对象。
public class NotifyProxy extends Notify{ //代理类持有被代理类的引用 private Notify notify; public NotifyProxy(Context context) { super(context); //根据版本实例化不同的被代理对象 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ notify = new NotifyHeadUp(context); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ notify = new NotifyBig(context); } else { notify = new NotifyNormal(context); } } @Override public void send() { notify.send(); } @Override public void cancel() { notify.cancel(); }}
12345678910111213141516171819202122232425261234567891011121314151617181920212223242526 客户端类:也就是我们的Activity,在 Activity 中,我们直接将 Context 传入,代理类会帮我们实例化合适的被代理对象。
new NotifyProxy(NotifyActivity.this).send();
11 在这个实例中,我们通过代理模式 ,使用一个代理类来针对不同的运行时系统版本,实例化不同的 Notificaition 的子类,而在客户端中 简单地调用代理类的send方法就可以。
静态代理模式总结
代理模式通过代理类对外部提供统一的接口,在代理类中实现对被代理类的附加操作,从而可以在不影响外部调用的情况下实现系统的拓展,我觉得代理模式可能在一个程序项目的开发初期运用不到,而在项目成型而又有了新的变化、升级等,可以考虑用代理模式来实现,这样可以不需要修改原有的代码。
动态代理模式
其实上文所讲述的内容只是代理模式的一部分,代理模式还有更为强大的动态代理模式。以下是这 2 个的区别:
静态代理模式:在我们的代码运行前,代理类的class编译文件就已经存在了 动态代理模式:在 code 阶段并不存在被代理类,而且并不知道要代理哪个对象,利用 java 的反射机制在运行期动态地生成代理者的对象,代理谁将会在代码执行阶段决定。
动态代理模式的实现
Java 已经为我们提供了一个便捷的动态代理接口 InvocationHandler ,我们重写其调用方法 invoke。以前面 我们的代理律师的例子为例,看看具体的操作是怎么样的
public class DynamicProxy implements InvocationHandler{ private Object object;// 被代理类的类引用 public DynamicProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforeDefend(); //调用被代理类对象的方法 Object result = method.invoke(object, args); afterDefend(); return result; } private void beforeDefend(){ System.out.print("我是原告律师,以下是我方辩词"); } private void afterDefend(){ System.out.print("辩护完毕"); }}
1234567891011121314151617181920212223242512345678910111213141516171819202122232425 解释一下这个 invoke 方法,我们通过 invoke 方法来调用具体的被代理方法,也就是真实的方法,如果对反射机制了解的话,method.invoke(object,args) 这句代码应该很好理解,object 是被代理类,method 是被代理类的方法,args 是方法的参数。
我们仅仅是实现了 InvocationHandler 的接口,那么接下来该做什么呢?怎么实现动态生成代理者对象呢?Java 的 java.lang.reflect 包下 还有一个 Proxy 类,它有个静态方法 newProxyInstance(),我们使用这个方法生成。以前面 我们的代理律师的例子为例,
public class Client { public static void main(String[] args) { //被代理类 IDefender accuser = new Accuser(); accuser.defend(); DynamicProxy proxy = new DynamicProxy(accuser); ClassLoader loader = accuser.getClass().getClassLoader(); IDefender proxyIDefender = (IDefender) Proxy.newProxyInstance(loader, new Class[]{IDefender.class}, proxy); proxyIDefender.defend(); }}
12345678910111213141234567891011121314 这个客户端的输出结果,和之前是一样的,可以发现,我们将之前代理类的工作,转换到 InvocationHandler 的 invoke() 方法去执行,不再需要关心到底需要代理谁。
动态代理在 Retrofit 框架中的运用
Retrofit 是 Android 上流行的 Http Client请求库先看以下一段代码
interface GitHubService { @GET("/repos/{owner}/{repo}/contributors") List<Contributor> repoContributors( @Path("owner") String owner, @Path("repo") String repo);}
123456123456Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .build();//代理模式的运用GitHubService service = retrofit.create(GitHubService.class);
1234512345Call<List<Repo>> repos = service.repoContributors("owner","repo");
11 由于我们要研究的方向是动态代理模式,所以我们直接深入主题,看一下这段代码
GitHubService service = retrofit.create(GitHubService.class);
11 GitHubService 是个接口,它作为Retrofit.create()方法的参数传入,这个方法的调用的返回对象 是个 GitHubService 的实例,那么它是怎么实现的呢?以下是 create()方法的代码,看没看到 Proxy.newProxyInstance() ,和 InvocationHandler()。同样,在这里 Retrofit 通过 注解 和 动态代理,用户只需要使用 注解 作用在抽象方法和抽象方法的参数上申明,这些 注解、方法参数、 方法返回值类型就提供了一次网络请求所需的信息,而具体的操作是由Retrofit通过解析这些信息,在运行期生成代理对象去调用。
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } return loadMethodHandler(method).invoke(args); } }); }
1234567891011121314151617181920212212345678910111213141516171819202122动态代理模式总结
动态代理模式在代码的运行阶段才生成 代理类对象,动态代理模式运用在需要对访问做特殊处理,比如对某个方法的调用加入权限验证;或者是对原来的方法进行统一的拓展,比如加入日志记录等,代理模式还被运用在实现 AOP ,大家可以去了解一下。