使用组合的设计模式 —— 追女孩要用的远程代理模式

这是设计模式系列的第三篇,系列文章目录以下:java

  1. 一句话总结异曲同工的设计模式:工厂模式=?策略模式=?模版方法模式android

  2. 使用组合的设计模式 —— 美颜相机中的装饰者模式编程

  3. 使用组合的设计模式 —— 追女孩要用的远程代理模式设计模式

  4. 用设计模式去掉不必的状态变量 —— 状态模式缓存

上一篇讲了一个使用组合的设计模式:装饰者模式。它经过继承复用了类型,经过组合复用了行为,最终达到扩展类功能的目的。bash

这一篇的代理模式也运用了组合的实现方法,它和装饰者模式很是像,比较它们之间微妙的差异很是有意思。网络

干吗要代理?

代理就是帮你作事情的对象,为啥要委托它帮你作?由于作这件事太复杂,有一些你不须要了解的细节,因此将它委托给一个专门的对象来处理。ide

就比如现实生活中的签证代理,各国办签证的须要的材料和流程不尽相同,有一些及其复杂。因此委托给了解这些细节的签证代理帮咱们处理(毕竟还有一大推bug等着咱们)。post

在实际编程中,复杂的事情可能有这么几种:远程对象访问(远程代理)、建立昂贵对象(虚拟代理)、缓存昂贵对象(缓存代理)、限制对象的访问(保护代理)等等。ui

本地 & 远程

java 中,远程和本地划分的标准是:“它们是否运行在同一个内存堆中”

  • 一台计算机上的应用经过网络调用另外一台计算机应用的方法叫作远程调用,由于两个应用程序运行在不一样计算机的内存堆中。
  • Android 系统中,每一个应用运行在各自的进程中,每一个进程有独立的虚拟机,因此它们运行在同一台计算机内存的不一样堆中,跨进程的调用也称为远程调用。

远程调用 & 远程代理

远程调用比本地调用复杂,由于须要处理本地和远程的通讯(网络或跨进程调用)。

调用的发起者其实不必了解这些细节,它最好只是简单地发起调用而后拿到想要的结果。因此将这些复杂的事情交给代理来作。(固然也能够将发起远程调用的细节和调用发起的业务逻辑写在一块儿,面向过程的代码就是这样作的)

就以 Android 中的跨进程通讯为例:发起调用的应用称为客户端,响应调用的应用称为服务端。服务以接口的形式定义在一个后缀为aidl的文件中:

//如下是IMessage.aidl文件的内容
package test.taylor.com.taylorcode;
interface IMessage {
    //系统本身生成的接口
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
    //这是咱们定义的服务接口
    int getMessageType(int index) ;
}
复制代码

系统会自动为IMessage.aidl文件生成对应的IMessage.java文件:

public interface IMessage extends android.os.IInterface {
    //桩
    public static abstract class Stub extends android.os.Binder implements test.taylor.com.taylorcode.IMessage {
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        //客户端调用这个接口获取服务
        public static test.taylor.com.taylorcode.IMessage asInterface(android.os.IBinder obj) {
            //建立代理对象(注入远程对象obj)
            return new test.taylor.com.taylorcode.IMessage.Stub.Proxy(obj);
        }
        
        //代理
        private static class Proxy implements test.taylor.com.taylorcode.IMessage {
            //经过组合持有远程对象
            private android.os.IBinder mRemote;
            //注入远程对象
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
            
            //代理对象对服务接口的实现
            @Override
            public int getMessageType(int index) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    //包装调用参数
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(index);
                    //发起远程调用(经过一些natvie层方法最终会调用服务端实现的stub中的方法)
                    mRemote.transact(Stub.TRANSACTION_getMessageType, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
    }
}
复制代码

(为了聚焦在代理这个概念上,代码省略了大量无关细节。)

系统自动生成了两个跨进程通讯关键类:Stub桩Proxy代理。它们是 Android 跨进程通讯中成对出现的概念。是服务端对服务接口的实现,代理是客户端对于桩的代理。 晕了。。为啥要整出这么多概念,搞这么复杂?

实际上是为了简化跨进程通讯的代码,将跨进程通讯的细节封装在代理中,客户端能够直接调用代理类的方法(代理和客户端处于同一内存堆,因此也称为远程的本地代理),由代理发起跨进程调用并将结果返回给客户端。代理扮演着屏蔽复杂跨进程通讯细节的做用,让客户端觉得本身直接调用了远程方法。

桩和代理拥有相同的类型,它们都实现了服务接口IMessage,但桩是抽象的,具体的实现会放在服务端。服务端一般会在 Android 系统组件 Service 中实现桩:

public class RemoteServer extends Service {
    public static final int MESSAGE_TYPE_TEXT = 1;
    public static final int MESSAGE_TYPE_SOUND = 2;
    
    //实现桩
    private IMessage.Stub binder = new IMessage.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {}

        //定义服务内容
        @Override
        public int getMessageType(int index) throws RemoteException {
            return index % 2 == 0 ? MESSAGE_TYPE_SOUND : MESSAGE_TYPE_TEXT;
        }
    };

    //将服务实例返回给客户端
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}
复制代码

客户端经过绑定服务来获取服务实例:

IMessage iMessage;
Intent intent = new Intent(this, RemoteServer.class);
ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        //将服务实例(桩)传递给asInterface(),该方法会建立本地代理并将桩注入
        iMessage = IMessage.Stub.asInterface(iBinder);
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        iMessage = null;
    }
};
//绑定服务
this.bindService(intent, serviceConnection, BIND_AUTO_CREATE);
复制代码

当绑定服务成功后,onServiceConnected()会被回调,本地代理会被建立,而后客户端就能够经过iMessage.getMessageType()请求远程服务了。

远程代理模式 vs 装饰者模式

远程代理运用了和装饰者模式一摸同样的实现方式,将它们俩的描述放在一块儿会显得颇有趣:

  • 装饰者和被装饰者具备相同的类型,装饰者经过组合持有被装饰者
  • 代理和被代理者具备相同的类型,代理经过组合持有被代理者

头大。。。既然同样为啥还要区分红两种模式?但若是结合它们的意图进行比较就能发现细微的差异:

  • 装饰者模式经过 继承 + 组合 的方式,在复用原有类型和行为的基础上为其扩展功能
  • 远程代理模式经过 继承 + 组合 的方式,实现对代理对象的访问控制

若是硬要用装饰者模式的台词来形容代理模式也没有什么不能够:“代理经过装饰被代理者,为其扩展功能,使得它可以被远程对象访问”。这句话彻底说得通,可是有点怪怪的。

若是试着添加一点拟人色彩,远程代理模式和装饰者模式就变得很好区分!

  • 使用代理模式就好像在说:“我喜欢你,可是我够不到你。因此我须要代理(多是你的闺蜜)”。
  • 使用装饰者模式就好像在说:“我喜欢和你相同类型的另外一我的。因此我须要把你装饰成它。”(好了,你找不到女友了)

后续

本打算用 1篇文章来总结那些使用组合的设计模式,其中包括装饰者模式、代理模式、适配器模式、外观模式、状态模式。

千千没想到写着写着就变成了n 篇。。。。

万万没有想到,这一篇代理模式写着写着就发现,若是把全部应用场景讲完,篇幅就太长了,无奈之下只能在此留白。代理模式的变种特别多,它们之间在实现方式上和意图上有微妙的差异,待下回分析。

相关文章
相关标签/搜索