跳转到主要内容
Chinese, Simplified

category

在编程中,有无数种生成和使用数据的方法,我只想探讨我个人非常钟爱的两种。选择哪一种取决于你的具体需求。在这篇文章中,我将解释它们的适用场景和主要区别。

 

按需供应(Supply on Demand)

 

 

核心思想:

  • 生产者/函数(Supplier/Function)被动 的。
  • 调用者/消费者(Caller/Consumer)主动 的。

这是编程中最常见的数据供应方式。在这种设计模式中,当观察者提出需求时,生产者才会提供数据。实现这种方法非常简单,可以使用一个方法和一对受控循环来构建。当调用者准备好处理生成的值时,它会调用一个函数,该函数生成数据并将其返回给调用者。另一种稍微不同的方式是使用“生产者/消费者”(Supplier/Consumer)对:将一个消费者回调方法传递给生产者,然后当数据准备好时,生产者会用包含生成数据的参数来调用该消费者。但这两种方式的核心都是按需生成数据,由调用者/消费者来决定何时获取数据。

实现示例:

  • Java 中的 Supplier/Function 接口
  • HTTP API 实现
  • 许多语言中的 Generators/Iterators(生成器/迭代器)

当然,这种设计还有许多子类型,你需要根据自己的需求进行选择。我列举几个例子:

  • 如果数据是有限的,必须有一个通用的控制机制来终止循环,例如当供应结束时返回一个 “null” 值。当然,这不总是适用,因为有时供应的数据本身就可能为 null。一个常见的方法是使用一个带有控制标志的封装对象来返回结果。
  • 如果生产者函数在其逻辑内部调用了另一个生产者,最好将一个回调函数作为处理程序传递给它,用于消费迭代值。生产者会将这个消费者回调传递给其所依赖的其他生产者。
  • 如果你的实现需要一个可以暂停和稍后恢复的循环,你可能需要一个变量来保存数据生成的下一步状态。每个生产者都必须将这个变量传递给其他递归的生产者。

关于这种设计的一些重要注意事项:

  • 不要让调用者等待异步数据生成。如果数据尚未就绪,只需通知调用者即可。另外,由于其本质,将这种设计用于异步数据生成通常不是一个好主意。如果你必须实现异步数据生成,请参阅下一章节。









    准备好时分享(Share when ready)


    核心思想:

    • 可观察对象/主题(Observable/Subject)主动 的。

    • 观察者/订阅者(Observer/Subscription)被动 的。

    这种设计模式使得数据在生成后会立即被发送。这种方法非常适合处理异步数据源。

    实现示例:

    • ReactiveX 框架

    • JavaScript 中的 Promises

    • 回调函数(Callbacks)

    • 一些 WebSocket 或 HTTP 轮询 API 实现

    取消(Cancellation)

    任何好的数据生成算法都应该能够被取消。没有人希望一个函数在结果不再需要时还无限循环下去。当一个函数超时或一个生成器循环不再必要时,让它停止工作是非常重要的,比如一个耗时长的 HTTP 请求,或者一个每秒发出当前时间戳的间隔函数。

    当我们谈论响应式流时,当没有活跃的订阅者时,可观察对象必须停止并清理正在运行的函数。一个设计良好的响应式流会在没有活跃订阅者时被取消。但关键是保持响应式流的“响应性”

    避免使用自定义的可观察对象实例。

    “它就在某个地方。”

    不要放弃寻找一种通用的、正确的方式来实现一个完全符合你要求的可观察对象。总会有一个响应式的解决方案。如果你选择实现一个拥有自定义订阅处理程序的可观察对象,你很可能遗漏一些关键点,导致你的可观察对象在订阅被取消后仍然继续工作。当然,这也会降低代码的可读性。实际上,这种方法应该应用于所有构建事物的领域,尤其是在编程中。我知道(我非常清楚)编写那些能实现惊人功能的代码是无价的乐趣,但如果你走这条路,就要准备好面对巨大的错误(GIANT BUGS)

    如果你正在使用 RxJS 库,可以看看这个设计精巧的决策树:

    rxjs-dev.firebaseapp.com/operator-decision-tree

    缓冲(Buffering)

    缓冲是另一个编程的噩梦。当生产者疯狂地生成数据而消费者无法处理海量数据时,或者当消费者/观察者还没有准备好接收数据时,缓冲可以帮助我们。我们可以将生成的数据进行缓冲,让消费者在准备好时再取用。这听起来很公平,但事实并非如此。这个故事中失败的部分不是缓冲本身,而是迫使我们使用缓冲的算法。如果你经常使用缓冲区,是时候重新审视你的设计了。你可能在需要使用**“响应式方法”(Reactive Approach)时使用了“按需供应”(Supply on Demand)**方式,反之亦然。如果一个数据源生成数据的速度超过了消费者的处理极限,你就应该使用“按需供应”设计。当消费者准备好接收新数据时,它应该向生产者请求数据。

    当多个消费者/观察者从一个生产者/可观察对象获取数据时,可能会出现其中一个消费者/观察者导致数据流变慢的情况,因为生产者/可观察对象会等待所有消费者/观察者处理完数据并准备好接收新数据。你可以使用缓冲技术来应对这种情况。这样,处理速度更快的消费者/观察者将能获取新数据,而其他消费者则仍在处理旧数据。系统中处理旧数据的消费者与最新数据的消费者之间的距离可以根据系统的可用资源或缓冲区的大小而变化。

    打破限制:混合设计(Breaking the limitations: Hybrid Design)

    想一想,如果我们能结合这两种设计模式的优点呢?通过结合这两种模式,可以实现一种既能取消、又能缓冲、还能避开瓶颈的单一设计。在 NodeJS 中,“**流”(Stream)**就是一个很好的例子。数据生成器可以是生产者(supplier)或可观察对象(observable),这取决于你的逻辑。如果它是一个可观察对象,并且达到了 “highWaterMark”(缓冲区的最大大小),读取器会通知生成器。这样生成器就会停止生成数据或通知另一个相关的生成器。如果它是一个生产者,只要订阅处于活跃状态,读取器就会在每次供应后请求下一个数据。

本文地址
最后修改
星期二, 九月 23, 2025 - 20:53
Article