The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:
你显然知道什么是延迟加载,对吧?而且你肯定也知道缓存。据我所知,在Java中实现它们没有优雅的方式。下面是我通过使用Cactoos原语自己找到的解决方法。
假设我们需要一个对象来加密一些文本。用面向对象的方式来说,它将封装文本并成为其加密形式。下面是我们将如何使用它的方式(首先创建测试):
现在让我们以一种非常原始的方式来实现它,只使用一个主构造函数。加密机制将简单地对传入数据中的每个字节加+1
,并假设加密不会破坏任何东西(这是一个非常愚蠢的假设,但为了这个例子的完整性,它可以工作)。
看起来正确吗?我测试过,它是有效的。如果输入是"Hello, world!"
,输出将是"Ifmmp-!xpsme\""
。
接下来,我们假设我们的类接受一个InputStream
和一个String
。我们想要像这样调用它,例如:
这是最明显的实现方式,有两个主要的构造函数(再次强调,该实现是原始的,但是有效)。
从技术上讲,它可以工作,但是流读取是在构造函数内部执行的,这是一种不好的实践。主要构造函数不应该做任何事情,只能进行属性赋值,而次要构造函数只能创建新对象。
让我们尝试重构并引入延迟加载:
运行得很好,但是外观很丑。其中最丑的部分当然是这两行:
它们使对象可变,并且它们使用NULL。相信我,这很丑陋。不幸的是,惰性加载和NULL引用总是同时出现在经典示例中。然而,有一种更好的实现方式。让我们重构我们的类,这次使用来自Cactoos的Scalar
。
现在看起来好多了。首先,只有一个主要构造函数和两个次要构造函数。其次,该对象是不可变的。第三,还有很大的改进空间:我们可以添加更多的构造函数,接受其他数据源,例如File
或字节数组。
简而言之,被认为是“懒加载”的属性在对象内部表示为一个“函数”(Java 8中的lambda表达式)。在我们未接触该属性之前,它不会加载。一旦我们需要使用它,该函数将被执行,然后我们得到结果。
然而,这段代码存在一个问题。每次调用asString()
时,它都会读取输入流,这显然是行不通的,因为只有第一次流中才有数据。在后续的每次调用中,流将为空。因此,我们需要确保this.text.value()
只执行封装的Scalar
一次。后续的所有调用必须返回先前计算的值。所以我们需要将其缓存起来。下面是如何实现:
此 StickyScalar
会确保只有第一次调用它的 value()
方法会传递给封装的 Scalar
。其他所有调用将接收到第一次调用的结果。
解决的最后一个问题是并发性。我们上面的代码不是线程安全的。如果我创建一个 Encrypted5
的实例并将其传递给两个同时调用 asString()
的线程,结果将是不可预测的,这仅仅是因为 StickyScalar
不是线程安全的。然而,还有另一个原始的方法可以帮助我们,叫做 SyncScalar
:
现在我们很安全,而且设计优雅。它包含了懒加载和缓存。
我现在在许多项目中都使用这种方法,它似乎方便、清晰且面向对象。
Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 05:19