【阿里大神讲设计模式】6.代理模式

本系列文章由阿里大神 **anly_jun **授权发布
140套Android优秀开源项目源码,领取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A

[TOC]

前情提要

上集讲到, 小光利用装饰者模式调校好了饮品加料(糖, 冰, 蜂蜜...)的流程. 从此再也不怕客户的各种要求了. 各式饮品也成了小光热干面店的一大特色.

当然, 饮品的试喝也不是无期限了. 试喝期快结束了, 小光跟表妹商量了下, 结合顾客们的反馈, 他们选定了其中三家, 到底使用哪家还需要跟商家再谈判下决定.

所有示例源码已经上传到Github, 戳这里

小光的烦恼

临近和供应商的谈判期了, 小光有点发怵了. 以往都是跟计算机打交道, 与人交道少, 何况还是商场...虽然自己是采购方, 不免还是有点虚.

先来看看要签单的小光:

// 照例抽象了一个Person接口:
public interface Person {
    /**
     * 签单
     * @param price
     */
    void signing(int price);
}

// 心底发虚的小光:
public class XiaoGuang implements Person {
    @Override
    public void signing(int price) {
        System.out.println("小光以" + price + "每箱的价格签单.");
    }
}

大龙出场

于是, 小光找来了做建材生意的堂哥大龙, 让大龙帮忙去谈判. 大龙欣然接受, 带着小光的底线就去了.

// 大龙
public class DaLong implements Person {

    private Person person;
    public DaLong(Person person) {
        this.person = person;
    }

    @Override
    public void signing(int price) {
        System.out.println("对方报价:" + price);
        if (price < 100) {
            this.person.signing(price);
        }
        else {
            negotiate(price);
        }
    }
    public void negotiate(int price) {
        System.out.println("不接受, 要求降价" + (price - 80));
    }
}

大龙也继承自Person, 有签单的职责. 但是除了signing, 大龙还肩负谈判(negotiate)的职责. 在签单上也有一些限制(小光给他的底线是100每箱). 当然, 谈下来了, 签字还是需要小光签的.

谈判开始

大龙虽然比小光大不了几岁, 但可以说是商场老手了. 拿到小光的底线后, 他自己在这个基础上再砍了20, 然后去跟饮品供应商谈判了.

public class Demo {
    public static void main(String[] args) {
        DaLong daLong = new DaLong(new XiaoGuang());
        // 第一轮, 对方报价120.
        daLong.signing(120);
        // 第二轮, 对方报价100.
        daLong.signing(100);
        // 第三轮, 对方报价90.
        daLong.signing(90);
    }
}

酒桌上, 拉锯三轮, 拿下:

// output
对方报价:120
不接受, 要求降价40

对方报价:100
不接受, 要求降价20

对方报价:90
小光以90每箱的价格签单.

果然还是大龙技高一筹啊, 以比小光预期更少的价格成交.
小光也是从中学习到不少技巧...拜服大龙哥.

故事之后

照例, 故事之后, 我们用UML类图来梳理下上述的关系:


相比于之前的关系, 这个相对简单, 就两个角色, 小光和大龙, 都实现了Person接口. 关键点在于:

  • 大龙是直接和供应商打交道的, 但是实际的决策和行为(签单)是由小光来做的.
  • 也就是说大龙是小光的代理.

这就是我们所要说的代理模式:
为其他对象(小光)提供一个代理(大龙)以控制对这个对象的访问.

在我们这个例子, 由于小光怯场, 不方便直接和供应商谈判, 故而派出了代理大龙来直面供应商.

扩展阅读一

细心的同学可能有发现, 这个例子的模式貌似和前文装饰模式有点类似啊. 这里大龙也相当于给小光装饰上了新的职责(谈判negotiate):

public void negotiate(int price) {
    System.out.println("不接受, 要求降价" + (price - 80));
}

那么代理模式相比与装饰模式有什么区别呢?

让我们再带上重点符来重温下二者:

  • 代理模式旨在为一个对象提供一个代理, 来控制对这个对象的访问.
  • 装饰模式旨在为一个对象动态添加职责, 增加这个对象的行为/属性.

二者虽然都会有代理类/装饰类实际调用被代理对象/被装饰对象的行为. 然而代理模式重在控制, 而装饰模式重在添加.

例如本例中, 大龙代理了小光的签单(signing)行为, 但不仅仅是像装饰模式那样, 加上某些行为/属性后就交给小光处理的. 大龙还加了控制:

public void signing(int price) {
   System.out.println("对方报价:" + price);

   if (price < 100) {
       this.person.signing(price);
   }
   // 如果对方报价大于等于100的时候, 大龙并没有让小光处理.
   else {
       negotiate(price);
   }
}

如果对方报价大于等于100的时候, 大龙并没有让小光处理. 也就是说大龙是有控制权的.

扩展阅读二

上面说到大龙是有控制权的, 也就是说, 这种代理实际上是一种控制代理, 也可以称之为保护代理.

代理模式除了这种控制访问/保护性的, 常常用到的场景还有:

远程代理: 为一个在不同的地址空间的对象提供局部代表, 从而可以隐藏这个被代理对象存在于不同地址空间的事实. 这个代表有点类似于大使, 故而也可以称之为"大使模式".
智能引用代理: 在代理中对被代理对象的每个操作做些额外操作, 例如记录每次被代理对象被引用, 被调用的次数等. 有点像引用计数的感觉.

扩展阅读三

说到远程代理, 就有必要聊聊Android中著名的AIDL了. 熟悉AIDL的同学, 应该比较清晰了解了, AIDL就是一个典型的远程代理模式的运用.

创建一个简单的AIDL文件:

// IRemoteService.aidl
package com.anly.samples;

// Declare any non-default types here with import statements

interface IRemoteService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void signing(int price);
}

生成的文件IRemoteService.java文件如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/mingjun/Dev/my_github/AndroidLessonSamples/app/src/main/aidl/com/anly/samples/IRemoteService.aidl
 */
package com.anly.samples;
// Declare any non-default types here with import statements

public interface IRemoteService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.anly.samples.IRemoteService {
        private static final java.lang.String DESCRIPTOR = "com.anly.samples.IRemoteService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.anly.samples.IRemoteService interface,
         * generating a proxy if needed.
         */
        public static com.anly.samples.IRemoteService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.anly.samples.IRemoteService))) {
                return ((com.anly.samples.IRemoteService) iin);
            }
            return new com.anly.samples.IRemoteService.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_signing: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _result = this.signing(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.anly.samples.IRemoteService {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int signing(int price) 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(price);
                    mRemote.transact(Stub.TRANSACTION_signing, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_signing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int signing(int price) throws android.os.RemoteException;
}

AIDL自动生成的java文件格式比较乱, 格式化了一下.
有几个关键点:

  1. IRemoteService是一个接口, 有signing方法
  2. IRemoteService中有一个静态抽象内部类Stub, 实现了IRemoteService接口.(集成了Binder再次就不讨论了)
  3. Stub中有一个asInterface方法, 返回一个IRemoteService, 实际上是Proxy.
  4. Stub中有一个私有的内部类Proxy.
  5. 这个内部类的机制是为了增加内聚, 一个AIDL生成的文件理论上是一个业务服务体系, 故而放在一起.

我们实现下Proxy和Stub, 然后用UML图来梳理下:


客户端AidlSampleActivity:

// 绑定服务的ServiceConnection中获取Proxy:
public void onServiceConnected(ComponentName name, IBinder service) {
  mRemoteService = IRemoteService.Stub.asInterface(service);
  isBound = true;

  if (mRemoteService != null) {
      try {
          mCurrentPrice = mRemoteService.signing(mCurrentPrice);
          mResult.setText("Result:" + mCurrentPrice);
      } catch (RemoteException e) {
          e.printStackTrace();
      }
  }
}

服务RemoteService继承Stub生成的IBinder:

private IRemoteService.Stub mBinder = new IRemoteService.Stub() {
   @Override
   public int signing(int price) throws RemoteException {
       int signingPrice = price - 10;
       Log.d("mingjun", "signing: " + signingPrice);
       return signingPrice;
   }
};

为了更清晰表达这是一个远程代理, 我们将RemoteService开辟在了其他进程中:

<service android:name=".aidl.RemoteService" android:process="com.anly.other"/>

本文不深入AIDL的应用和原理, 具体客户端(AidlSampleActivity)和服务(RemoteService)的代码就不贴了, 完整代码点这里.
OK, 这就是我们今天要说的代理模式.
有了代理模式, 尤其是远程代理, 小光发现有时候自己都可以不用亲临店了呢, 有了更多的时间出去考察新店地址了...哈哈.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 前情提要 上集讲到, 小光利用装饰者模式调校好了饮品加料(糖, 冰, 蜂蜜...)的流程. 从此再也不怕客户的各种...
    anly_jun阅读 937评论 5 11
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • Android跨进程通信IPC整体内容如下 1、Android跨进程通信IPC之1——Linux基础2、Andro...
    隔壁老李头阅读 10,718评论 13 43
  • 版权声明:本文为博主原创文章,未经博主允许不得转载 PS:转载请注明出处作者: TigerChain地址: htt...
    TigerChain阅读 1,690评论 0 2
  • 朝阳初上,夏日里难得的和风天气,一早家人围坐在一起吃了早餐,我便收拾好行囊准备出发,可爱的儿子一直舍不得我离开...
    瑜伽行者一澜阅读 198评论 0 0