前言
在 JavaScript 中,对于初学者,设计模式往往令人云里雾里,但实际上它就藏在我们日常的代码场景中。今天,让我们从一个生活化的例子——“送花”,聊聊代理模式(Proxy)到底是什么,以及如何在 JS 中用简单的对象字面量轻松实现。
一、一个简单的的送花场景
首先来看一段代码,这是一个典型的面向对象实现:
送花对象
let Fyang= {
name: '沸羊羊',
sendFlower: function(target) {
target.receiveFlower(Fyang); // 给目标送花
}
};
// 目标对象
let Myang = {
xq: 30, // 心情值,满分为100
name: '美羊羊',
receiveFlower: function(sender) {
console.log('美羊羊收到了' + sender.name + '送的花');
if (this.xq < 80) {
console.log('你是只好羊'); // 心情差就拒绝
} else {
console.log('我家猫会后空翻,你想去看看嘛'); // 心情好就接受
}
}
};
如果沸羊羊直接给美羊羊送花:
Fyang.sendFlower(Myang);
// 输出:美羊羊收到了沸羊羊送的花 → “你是只好羊”
结果并不美好,沸羊羊被发了好羊卡,因为美羊羊的心情值太低。这时候问题来了:如何在不改变美羊羊和沸羊羊原有代码的情况下,提高送花成功率?
二、代理模式的引入:喜羊羊的中间人作用
现实中遇到这种场景,我们一般会找一个双方的熟人先进行接触。在代码世界里,这个中间人就是 "代理模式":
// 喜羊羊作为代理
let Xyang = {
name: '喜羊羊',
receiveFlower: function(sender) {
// 3秒后再转发(模拟等待时机)
setTimeout(() => {
Myang.xq = 90; // 喜羊羊帮忙调节美羊羊心情
xm.receiveFlower(sender); // 转发花
}, 3000);
}
};
现在沸羊羊通过喜羊羊送花:
Fyang.sendFlower(Xyang);
// 3秒后输出:美羊羊收到了沸羊羊的花 → “我家猫会后空翻,你想去看看嘛”
输出结果改变了!这里的喜羊羊就是一个代理对象,他实现了和美羊羊相同的 receiveFlower 方法(这就是接口一致性),却在中间做了额外操作——调节美羊羊的心情。
三、代理模式的核心:接口一致性 + 增强行为
从这个例子里,我们可以提炼出代理模式的核心要素:
接口一致性
代理对象(喜羊羊)和目标对象(美羊羊)必须拥有相同的方法(receiveFlower),这样调用者(沸羊羊)才不需要区分到底在调用谁。这就是面向接口编程的精髓——调用者只关心“能做什么”,不关心“具体是谁在做”。
增强行为
代理可以在不修改目标对象的前提下,添加额外操作。比如喜羊羊做的:
- 延迟执行(等待合适时机)
- 修改目标状态(提高心情值)
- 甚至可以拒绝转发(如果判断送花肯定失败)
职责分离
目标对象(美羊羊)只负责核心逻辑(收花后的反应),代理(喜洋洋)负责辅助逻辑(时机判断、状态调节),符合单一职责原则。
四、JavaScript 中的语言特性
为什么 JS 特别适合实现代理模式?这和它的语言特性密不可分:
- 对象字面量的灵活性:不需要像 Java 那样先定义接口类,直接用
{}就能创建拥有相同方法的对象,轻松实现接口一致性。 - 函数方便传递:方法可以像变量一样传递,让代理的方法转发变得极其简单。
- 弱类型特性:不需要严格的类型检查,只要两个对象有相同方法就能互相替代,降低了代理模式的实现成本。
五、总结
代理模式是一种结构型设计模式,它提供了一个对象,通过它可以控制对另一个对象的访问。代理对象作为客户端和目标对象之间的中介,可以用于添加各种功能,而无需修改目标对象本身。
其核心思想就是引入一个“中间层”来管理对原始对象的访问,从而在不改变原始对象代码的前提下,增强其功能或控制其行为。
当下次需要写代理模式时,不妨从这个送花案列出发。