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

简介: 上篇讲了一个使用组合的设计模式:装饰者模式。它通过继承复用了类型,通过组合复用了行为,最终达到扩展类功能的目的。 代理模式也运用了组合的实现方法,它和装饰者模式非常像,比较它们之间微妙的差别很有意思。

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

  1. 一句话总结殊途同归的设计模式:工厂模式=?策略模式=?模版方法模式
  2. 使用组合的设计模式 —— 美颜相机中的装饰者模式
  3. 使用组合的设计模式 —— 追女孩要用的远程代理模式
  4. 用设计模式去掉没必要的状态变量 —— 状态模式

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

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

干嘛要代理?

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

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

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

本地 & 远程

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 篇。。。。

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

目录
相关文章
|
6月前
|
设计模式 缓存 监控
【设计模式系列笔记】代理模式
代理模式是一种结构型设计模式,它允许一个对象(代理对象)控制另一个对象的访问。代理对象通常充当客户端和实际对象之间的中介,用于对实际对象的访问进行控制、监控或其他目的。
122 1
|
6月前
|
设计模式 缓存 安全
小谈设计模式(8)—代理模式
小谈设计模式(8)—代理模式
|
2月前
|
设计模式 缓存 安全
设计模式——代理模式
静态代理、JDK动态代理、Cglib 代理
设计模式——代理模式
|
6月前
|
设计模式 Java
Java一分钟之-设计模式:装饰器模式与代理模式
【5月更文挑战第17天】本文探讨了装饰器模式和代理模式,两者都是在不改变原有对象基础上添加新功能。装饰器模式用于动态扩展对象功能,但过度使用可能导致类数量过多;代理模式用于控制对象访问,可能引入额外性能开销。文中通过 Java 代码示例展示了两种模式的实现。理解并恰当运用这些模式能提升代码的可扩展性和可维护性。
66 1
|
2月前
|
设计模式 Java 数据安全/隐私保护
Java设计模式-代理模式(7)
Java设计模式-代理模式(7)
|
6月前
|
设计模式 Java 数据库连接
【重温设计模式】代理模式及其Java示例
【重温设计模式】代理模式及其Java示例
|
3月前
|
设计模式 缓存 Java
【十一】设计模式~~~结构型模式~~~代理模式(Java)
文章详细介绍了代理模式(Proxy Pattern),这是一种对象结构型模式,用于给对象提供一个代理以控制对它的访问。文中阐述了代理模式的动机、定义、结构、优点、缺点和适用环境,并探讨了远程代理、虚拟代理、保护代理等不同代理形式。通过一个商务信息查询系统的实例,展示了如何使用代理模式来增加身份验证和日志记录功能,同时保持客户端代码的无差别对待。此外,还讨论了代理模式在分布式技术和Spring AOP中的应用,以及动态代理的概念。
【十一】设计模式~~~结构型模式~~~代理模式(Java)
|
3月前
|
设计模式
设计模式的基础问题之代理模式在工作中的问题如何解决
设计模式的基础问题之代理模式在工作中的问题如何解决
|
4月前
|
设计模式 算法 Go
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
|
4月前
|
设计模式 缓存 JavaScript
js设计模式【详解】—— 代理模式
js设计模式【详解】—— 代理模式
32 0
下一篇
无影云桌面