介绍
代理模式(Proxy Patter)也称委托模式,属于结构型设计模式。这是一个相当重要的设计模式,Android源码中随处可见。何为代理模式呢?比如每天吃饭时赶进度是常事,就叫公司的同时帮忙买饭,这就是一种代理;如果碰到辞职时老板不给发工资,还得委托个律师 帮你打官司,这也是代理。总之,即便在现实生活中,代理也是无处不在。
定义
为其他对象提供一种代理以控制这个对象的访问。
使用场景
当无法或者不想直接访问某个对象 或者 访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。
代理模式通用模板代码
//抽象类主题
public abstract class Subject {
//一个普通的业务方法
public abstract void visit();
}
//实现抽象主题的真实主题类
public class RealSubject extends Subject {
@Override
public void visit() {
//RealSubject中visit的具体逻辑
System.out.println("Real subject!");
}
}
//代理类
public class ProxySubject extends Subject {
//持有真实主题的引用
private RealSubject mSubject;
public ProxySubject(RealSubject mSubject) {
this.mSubject = mSubject;
}
@Override
public void visit() {
//通过真实主题引用的对象调用真实主题中的逻辑方法
mSubject.visit();
}
}
//客户类
public class Client {
public static void main(String[] args) {
//构造一个真实的主题对象
RealSubject real = new RealSubject();
//通过真实主题对象构造一个代理对象
ProxySubject proxy = new ProxySubject(real);
//调用代理的相关方法
proxy.visit();
}
}
角色介绍:
Subject:抽象主题类。该类的主要职责是声明真实主题与代理的共同接口方法,该类既可以是一个抽象类也可以是一个接口。
RealSubject:真实主题类。该类也被称为被委托类或者被代理类,该类定义了代理所表示的真实对象,由其执行具体的业务逻辑方法,而客户类则通过代理类间接调用真实主题类中定义的方法。
ProxySubject:代理类。该类也成为委托类或者代理类。该类持有一个对真实主题类的引用。在其所实现的接口方法中调用真实主题类中相应的接口方法执行,以此起到代理的作用。
客户类。使用代理类的类型。
代理模式的简单实现
正如文章开头所述,你可以在日常生活中找到代理模式的例子。就拿拖欠工资为例,这种情况就应该通过法律途径解决,那么律师就是自己的诉讼代理人,我们将诉讼的流程抽象在一个接口类中:
//诉讼接口类
public interface ILawsuit {
// 提交申请
void submit();
// 进行举证
void burden();
//开始辩护
void defend();
//诉讼完成
void finish();
}
四个方法非常简单,它对应着我们之前模板中的抽象主题,它用来声明真实主题(本人)与代理(律师)的共同接口方法。
下面是本人,也就是具体诉讼人:
public class XiaoMing implements ILawsuit {
@Override
public void submit() {
//老板拖欠小明工资 小明只好仲裁
System.out.println("老板拖欠工资!特此申请仲裁!");
}
@Override
public void burden() {
//小明证据充足,不怕告不赢
System.out.println("这是合同书和过去一年的银行工资流水!");
}
@Override
public void defend() {
//铁证如山,辩护也没啥好的
System.out.println("证据确凿!不需要在说啥了!");
}
@Override
public void finish() {
//结果也是肯定的,必赢
System.out.println("诉讼成功!判决即日起七天内结算工资!");
}
}
如上所述,该类实现ILawsuit并对其中4个方法作出具体实现逻辑,当然小明是不会自己打官司的,需要请个代理律师。
//代理律师
public class Lawyer implements ILawsuit {
//持有一个具体被代理者的引用
private ILawsuit mLawsuit;
public Lawyer(ILawsuit lawsuit) {
this.mLawsuit = lawsuit;
}
@Override
public void submit() {
mLawsuit.submit();
}
@Override
public void burden() {
mLawsuit.burden();
}
@Override
public void defend() {
mLawsuit.defend();
}
@Override
public void finish() {
mLawsuit.finish();
}
}
律师类中持有一个被代理者(比如小明),律师所执行的方法实质就是简单地调用被代理者中的方法,下面看看客户端:
//Client
public class Client {
public static void main(String[] args) {
//构造一个小明
ILawsuit xiaoming = new XiaoMing();
//构造一个代理的律师并将小明作为构造参数传递进去
ILawsuit lawyer = new Lawyer(xiaoming);
//省略。。。
}
}
代理除小明外的其他人行吗?当然可以,我们只需要再定义一个XiaoHui类来实现ILawsuit即可。
代理模式可以分为两类,一类是静态代理,一类是动态代理。上面介绍的就是静态代理。在静态代理中,代理者的代码由程序员自己或通过一些自动化工具生成固定的代码在对其进行编译,也就是说在我们的代码运行前代理类的class文件就已经存在;而动态代理则与静态代理相反,通过反射机制动态地生成代理者的对象,也就是我们在code阶段根本就不知道要代理谁,代理谁将会在我们真正执行阶段决定。而Java也给我们提供了一个便捷的代理接口InvocationHandler,实现该接口需要重写其调用方法invoke。
//JDK提供的专门用于实现动态代理的接口InvocationHandler ,需要实现它的invoke方法。
public class DynamicProxy implements InvocationHandler {
//通过invoke方法来调用具体的被代理方法,也就是真实的方法
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable{
return null;
}
}
动态代理可以使我们的代码更加简洁,不过在这之前需要首先完善动态代理类。
public class DynamicProxy implements InvocationHandler {
//被代理的类的引用
private Object obj;
public DynamicProxy(Object obj) {
this.obj = objl
}
//通过invoke方法来调用具体的被代理方法,也就是真实的方法
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable{
//在这里通过被代理类的引用来调用被代理类的方法
Object result = method.invoke(obj, args);
return result;
}
}
就像上面的代码所述,我们首先声明一个Object引用,该引用将指向被代理类,而我们调用被代理类的具体方法则在invoke方法中执行。也就是说,我们之前在静态代理中让代理律师所做的事情,现在在动态代理中由InvocationHandler来处理,不再需要关心到底代理谁。下面是客户类的逻辑:
public static void main(String[] args) {
//构造一个小明
ILawsuit xiaoming = new XiaoMing();
//构造一个动态代理
DynamicProxy proxy = new DynamicProxy(xiaoming);
//获取被代理类小明的ClassLoader
ClassLoader loader = xiaoming.getClass().getClassLoader();
//动态构造一个代理律师
ILawsuit laywer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[] {ILawsuit.class}, proxy);
//律师提交诉讼请求
lawyer.submit();
//律师进行举证
lawyer.burden();
//律师代替小明进行辩护
lawyer.defend();
//完成诉讼
lawyer.finish();
}
运行的结果和以前一样,由此可见动态代理可以通过一个代理类来代理N多个被代理类,其实质是对代理者与被代理者进行解耦,使两者之间没有直接的耦合关系。相对而言静态代理只能为给定接口下的实现类做代理,如果接口不同那么就需要重新定义不同的代理类,较为复杂,但是静态代理更符合面向对象原则,并不是说动态代理就是绝对的好。
根据是否可以在编译期确定被代理的对象,我们把代理模式分成了在静态代理和动态代理两类,除了这个区别,我们也可以从适用范围来区分不同类型的代理实现。
远程代理(Remote Proxy):为某个对象在不同的内存地址空间提供局部代理。使系统可以将Server部分的实现隐藏,以便Client可以不必考虑Server的存在而自由操作Server。
虚拟代理(Virtual Proxy):使用一个代理对象表示一个十分消耗资源的对象并在真正需要时才创建。
保护代理(Protection Proxy):使用代理模式控制对原始对象的访问。该类型的代理常被用于原始对象有不同访问权限的情况。
智能引用(Smart Reference):在访问原始对象时执行一些自己的附加操作并对指向原始对象的引用计数。
无论静态代理还是动态代理,都能应用于上述4种情况,两者是各自独立的。
代理模式实战
代理模式的应用广泛。我们平时开发应用时,使用代理模式的机会并不多。这里以不同版本的API发送通知为例来阐述我们平时在应用开发过程中对代理模式的应用。
Android中发送通知比较简单,我们只需要使用NotificationManager的notify方法发送一个通知即可:
public class ProxyActivity extends Activity implements View.OnClickListener {
private NotificationManager nm;
private NotificationCompat.Builder builder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notify_proxy);
nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_launcher).setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, NotifyActivity.class), PendingIntent.FLAG_UPDATE_CURRENT));
init(R.id.notify_proxy_send_btn);
}
private void init(int resId) {
Button b = (Button) findViewById(resId);
b.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch(view.getId()) {
case R.id.notify_proxy_send_btn:
Notification n = builder.build();
n.contentView = new RemoteViews(getPackageName(), R.layout.remote_notify_normal);
nm.notify(0, n);
break;
}
}
}
如上述代码所示,当点击布局中以notify_proxy_send_btn
为ID标识的按钮时就会发送一条通知,该通知我们使用了一个简单的自定义布局视图。
虽说Notification这块比较简单,但是适配器来却很麻烦,因为Android的API版本迭代较快,再加上厂商定制就更碎片化了。在开发中,主要用到4种Notification:
(1)一种是正常视图,也就是大家常看到的状态栏中出现的高度为64dp的长条状通知视图;
(2)一种是在API16中引入的以Style方式展示的MediaStyle、InboxStyle、BigTextStyle和BigPictureStyle的这四种风格样式;
(3)一种也是在API16中光阴如的可以将通知视图显示为256dp高度大视图的bigContentView;
(4)最后一种则是在L中加入的headsUpContentView。
在实际测试中bigContentView不能再API16、API17、API18中很好地显示,但大部分API19设备没有问题,锤子手机例外;而小米2虽然基于了Android5.0也就是API21定制,但是它也没有使用到Android5.0的Notification,那么对于这样一个碎片化较多的情况该如何通过代理模式去解决呢?这时候就可以使用代理模式:我们为每种不同的Notification样式定义一个类,这里以正常的64dpHeight、256dp Height 和 headsUpContentView为例,来演示代理模式的应用。
首先定义一个抽象类表示通知:
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.drawable.ic_launcher).setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, NotifyActivity.class), PendingIntent.FLAG_UPDATE_CURRENT));
}
//发送一条通知
public abstract void send();
//取消一条通知
public abstract void cancel();
}
Notify类就是代理的抽象主题它用来声明被代理类与代理类的共同接口方法。下面就是各个需要被代理的通知样式的实现类:
//64dp Height的Notification实现类(之前说的第一种正常视图的Notification)
public class NotificationNormal extends Notify {
public NotificationNormal(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = new RemoteViews(context.getPakageNAme(), R.layout.remote_notify_normal);
nm.notify(0, n);
}
@Override
public void cencel() {
nm.cancel(0);
}
}
下面是256dp的Height的bigContentView的被代理类:
public class NotifyBig extends Notify {
public NotifyBig(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal);
n.bigContentView = new RemoteViews(context.getPackageName(), R.layout.remote.notufy_proxy_big);
nm.notify(0, n);
}
@Override
public void cancel() {
nm.cencel(0);
}
}
最后对于L以上也就是API20开始的系统我们还可以为Notification增加一个headsUpContentView,当APP正在全屏运行时,如果此时收到通知,这个headsUpContentView会浮动展示于屏幕顶部,类似Toast,:
//headsUpContentView形式的Notification,对应于上面的第四种Notification
public class NotifyHeadsUp extends Notify {
public NotifyHeadsUp(Context context) {
super(context);
}
@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.notufy_proxy_big);
n.headsUpContentView = new RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal);
nm.notify(0, builder.build());
}
@Override
public void cancel() {
nm.cancel(0);
}
}
接着我们将定义一个实现了Notify抽象类的代理类:
public class NotifyProxy extends Notify {
private Notify notify;
public NotifyProxy(Context context) {
super(context);
//如果设备版本是5.0及以上,那么就执行NotifyHeadsUp的被代理类的业务逻辑
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notify = new NotifyHeadsUp(context);
}
//如果版本是 4.4-5.0(不包含)版本,那么就执行NotifyBig的被代理类的业务逻辑
else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
notify = new NotifyBig(context);
}
// 如果版本是4.3及以下版本,那么就执行NotifyNormal的被代理类的业务逻辑
else {
notify = new NotifyNormal(context);
}
}
@Override
public void send() {
notify.send();
}
@Override
public void cancel() {
notify.cancel();
}
}
在构造方法中,根据SDK版本的不同去实例化实例化不同的Notify子类,最终由该类的send和cancel方法去调用不同的逻辑实现,这样Activity就简单了:
public class ProxyActivity extends Activity extends View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notify_proxy);
init(R.id.notify_proxy_send_btn);
}
private void init(int resId) {
Button b = (Button) findViewById(resId);
b.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch(view.getId()) {
case R.id.notify_proxy_send_btn:
new NotifyProxy(this).send();
break;
}
}
}
这里的代码很简单,就不给出运行结果了。
总结
代理模式应用广泛,而且几乎没什么缺点,它是细分化至很小的一种模式,要非说它的缺点,那就是所有设计模式的通病:对类的增加。不过在它巨大的优点面前,这个缺点可以忽略不计了。