设计模式与生活⑦:你打的车,隔着一个平台

周五早上,闹钟还没响,人先醒了。

不是被吵醒的。是心里那根弦自己松了------睁眼就知道,今天上完,能歇两天。

周五这天,上班族的心情是有固定形状的。有人在群里发"今天周五",有人已经开始盘算晚上吃点什么。小王也高兴,但他高兴得有点早------今早没车骑,还得出门。

昨晚那版上线,收工很晚。出公司的时候他没去地铁站,直接打了辆车回家。电动车就那么停在地铁站旁边,锁都没动过。

所以今天早上没车骑。他得比平时早一点出门,打车去公司。

昨天一辆车送他回家,今天一辆车送他去上班

昨晚一趟,今早一趟。两趟车打下来,他琢磨出点意思:这一路,他其实没跟司机直接说上几句话。多少钱、走哪条路、车牌对不对,全是平台在管。

他叫的从来不是"一辆车",是平台。

今天聊的就是这个站在中间的"平台":代理(Proxy)------你和一个真正干活的对象之间,隔着一个替它接话的人。系列第七篇。

01 你压根没跟司机说上话

昨晚叫车,手机点两下,屏幕上跳出"预计 5 分钟"。

车到了,车牌对得上。上车,下车,付款。他没跟司机谈价,也没存司机的手机号------行程一结束,那个号码很快就不能拨了。

当时只觉得方便。今天再想,这里头有三件事一直在替他兜着:车牌得对上、号码不能外露、这一单得留痕。

如果代码里也这么直白地找个"真司机":

java 复制代码
// 直接叫真司机
Ride driver = new RealDriver("王师傅");
driver.go("家");

三行就能跑,规矩却全在调用的人脑子里

问题是这三件事没人管:谁都能 new 一个司机出来,绕过所有规矩;想统一加个录音、加个计价,得在每个调用点抄一遍;哪天要换个人开车,得把所有 new 的地方翻出来改。

直接调用最省事,也最没规矩。

生活里也一样。路边招手拦车,车能来,路能走。但绕不绕路、多少钱、发票怎么开、出了事找谁,全凭运气。

02 中间站一个平台

代理的写法,一句话:平台和司机实现同一个接口,平台手里握着司机。

java 复制代码
// 一个接口:把一段路走完
interface Ride { void go(String to); }

// 真司机:手握方向盘的那位
class RealDriver implements Ride {
    private final String name;
    RealDriver(String name) { this.name = name; }
    public void go(String to) {
        System.out.println(name + " 开车去 " + to);
    }
}

// 平台:你只跟它说话
class RidePlatform implements Ride {
    private final Ride driver;
    RidePlatform(Ride driver) { this.driver = driver; }

    public void go(String to) {
        String orderId = createOrder(to);      // 下单,先留痕
        openVirtualNumber();                   // 虚拟号,真号不外露
        driver.go(to);                         // 开车的还是司机
        settle(orderId);                       // 结算、录音归档
    }
}

你调平台,平台转头才去叫司机

调用只有两行:

java 复制代码
Ride ride = new RidePlatform(new RealDriver("王师傅"));
ride.go("家");

调用方眼里只有平台,司机藏在后面

重点不在"多套了一个类"。重点是:你按的是平台的按钮,平台转头才去叫司机。 中间那一段,就是代理的全部。

下单在平台,开车在司机,两边互不打听

司机知不知道有平台?知道。但他不关心是谁在叫他。平台也不关心路怎么绕。两边只通过 Ride 这一个接口说话。

以后加行程分享、加投诉入口,都在平台里加,司机那几行一个字不用动。

03 平台不止管一件事

平台做的事,拆开看是三件。

挡人。 你看到的司机电话是虚拟号,司机看到的你也一样。号码只用在这一单里,行程一结束就失效。这叫保护代理------它先替你把关,够格了才放行。

占位。 车还在几公里外,App 先给你车牌和预计到达时间。真的车没到,先给个东西占着位。这叫虚拟代理------代码里常见的样子是:真对象开销大,用到才创建。

记住了。 常用地址、上次走的哪条路、这单多少钱,不用每次重来。这叫缓存代理。

写在代码里,前两种长这样:

java 复制代码
// 挡人:先验资格,号码走虚拟的
class Protector implements Ride {
    private final Ride driver;
    Protector(Ride driver) { this.driver = driver; }
    public void go(String to) {
        if (!driverReady()) throw new IllegalStateException("司机还没到位");
        System.out.println("拨号走 " + virtualNumber());
        driver.go(to);
    }
}

// 占位:真司机用到才建,先不急着造
class Placeholder implements Ride {
    private Ride real;
    public void go(String to) {
        if (real == null) real = new RealDriver("王师傅");
        real.go(to);
    }
}

一个管放不放行,一个管什么时候真去建

三种名字不一样,做的事是同一件:在"你"和"真东西"之间,管住那道门。

这里有一处对不上,得挑明。打车平台是现实里的一家真公司,代理只是代码里的一个对象,跟真对象住在同一个进程里。平台会抽成、会调度几十上百辆车;代码里的代理,大多只守着它代理的那一个真对象。

平台比代理能干得多。真正对得上的,是"中间那一段":你想办事,得先经过它。

04 跟装饰器分清楚:一个加料,一个把门

第五篇给电动车加挡风被,今天打车。两件事看着都像"多了一层",其实差得远。

装饰器:车还是你骑,方向盘在你手里。挡风被包在外面,开春能拆,也能再套个把套。它给本体加能力。

代理:方向盘不在你手里。你坐后座,司机在开。你甚至不需要知道他是谁。它给访问加一道关卡。

java 复制代码
// 装饰器:本体外面加一层,还是你自己骑
Commute scooter = new WindshieldCover(new ElectricScooter());

// 代理:你只认识平台,方向盘在别人手里
Ride ride = new RidePlatform(new RealDriver("王师傅"));

包法一样,谁握方向盘不一样

左边方向盘在自己手里,右边已经交出去了

代码上的差别还能再细一层。

  • 装饰器一般由调用方自由叠,叠几层、什么顺序,组装的地方说了算;
  • 代理一般由框架定死,你调的就是代理,很多时候根本不知道后面还有没有真对象;
  • 装饰器基本原样转发;代理可以不转发------保护代理直接拒绝,虚拟代理先不创建真对象。

装饰器怕你不够用,代理怕你乱用。

05 JDK 里最正宗的代理

JDK 自己就带了一个:java.lang.reflect.Proxy。照着一个接口,它在运行时现场给你造一个代理对象,那个类你一个字都不用写。

java 复制代码
Ride real = new RealDriver("王师傅");

Ride platform = (Ride) Proxy.newProxyInstance(
    Ride.class.getClassLoader(),
    new Class<?>[]{Ride.class},
    (p, method, args) -> {
        System.out.println("平台记一笔:" + method.getName());
        return method.invoke(real, args);      // 最后动手的还是司机
    });

platform.go("家");

每次调用先被 lambda 接住,再转给真司机

中间那个 lambda 就是平台。调用全被它接住,记一笔,再转给真司机。连平台那个类都省了,JDK 现场替你造。

JDK 之外,形状也一样。RMI 里那个存根(stub),你以为在调本地方法,实际是通过网络发给了另一台机器,这是远程代理;Hibernate 查出来的关联对象,第一次访问才去查库,这是虚拟代理;MyBatis 的 Mapper 只写一个接口,实现类就是 Proxy 现场生成的;Spring 的事务和日志,套在 Service 外面,业务代码里看不见代理的影子。

壳是 JDK 给的,门里的规矩还得自己写。

06 什么时候,别乱叫代理

代理不是所有调用都该套。

自己家门口挪个车,犯不上叫平台。一次性的、不碰外部资源的、没人需要管着的调用,直接调就行。套上代理,本来一行能看懂的代码,得跳两层才知道谁在干活。

代理层也别塞业务。代理只管门,不管屋里的事。真把业务逻辑写进去,它就变成了第二个真对象,以后改需求得改两处,还容易改漏一处。

还有排查。调用链上多一个代理,报错栈就多一层。线上出问题,第一件事是先弄清这行到底是谁在执行。

这两年写代码,AI 套一层代理很快------注解一加,AOP 一配,看上去就完事了。

但它不会告诉你:这道门该不该有,谁该被挡在外面。

代理能替你把门,门朝哪开还得你自己定。

07 收工

周五下班,天还亮着。

他坐地铁到那一站,出站,走到停车的地方。电动车还在,车筐里落了点灰。钥匙一拧,车灯亮了一下------停了一天一夜,还有电。

骑回去的路上,风从耳边过。昨天打车,今早打车,绕了一圈,都是为了把这辆车接回自己手里。

这一路他也算把"代理"看明白了:真正干活的那位一直都在,只是你和它中间,多了一道门。

跟前面六篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把要做的事做成对象,代理管谁能碰到真东西、怎么碰。

带走一句话:代理 = 你和真正干活的那个之间,多一道门。

门可以一直开着,但让谁进,它说了算。


参考资料

相关推荐
geovindu2 小时前
rust: Read-Write Lock Pattern
开发语言·后端·设计模式·rust·读写锁模式·同步型模式
工一木子1 天前
设计模式与生活⑤:天冷了,给电动车套件挡风被
设计模式
是2的10次方啊1 天前
设计模式与生活⑥:问题一个个砸过来,先写张便签
设计模式
是2的10次方啊2 天前
设计模式与生活⑤:天冷了,给电动车套件挡风被
设计模式
昭昭日月明3 天前
AI 时代的《重构》
设计模式·架构
工一木子3 天前
设计模式与生活③:你的早晨,是一套模板
设计模式
是2的10次方啊3 天前
设计模式与生活④:冰箱空了,早餐换个买法
设计模式
知守观4 天前
设计模式实战:从真实老项目中看标准模板方法与工厂的落地
java·spring·设计模式
多弗朗皮卡丘4 天前
C++特殊类设计
开发语言·c++·设计模式