从沸羊羊送花,浅谈JavaScript的设计模式:代理模式

45 阅读4分钟

前言

在 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 那样先定义接口类,直接用 {} 就能创建拥有相同方法的对象,轻松实现接口一致性。
  • 函数方便传递:方法可以像变量一样传递,让代理的方法转发变得极其简单。
  • 弱类型特性:不需要严格的类型检查,只要两个对象有相同方法就能互相替代,降低了代理模式的实现成本。

五、总结

代理模式是一种结构型设计模式,它提供了一个对象,通过它可以控制对另一个对象的访问。代理对象作为客户端和目标对象之间的中介,可以用于添加各种功能,而无需修改目标对象本身。

其核心思想就是引入一个“中间层”来管理对原始对象的访问,从而在不改变原始对象代码的前提下,增强其功能或控制其行为。

当下次需要写代理模式时,不妨从这个送花案列出发。