Kotlin设计模式:Flyweight
Flyweight模式的目的
该模式主要用于平衡应用中的内存使用情况。这都与对象可重用性有关(请注意,我使用了“对象”一词,而不是“类”)。您有一个可以重复使用的类似对象池,而不是每次都创建新对象。
这样,您不必在每次创建对象时分配内存,而是分配一次内存并重用之前创建的对象。
这意味着您将在对象创建中节省一些CPU和内存,并使垃圾收集速度更快。然而,有一个权衡。根据实施情况,您必须:
控制从池中删除对象,这可能很棘手,就像您删除当前使用的对象一样,它会破坏东西。
为未使用的对象分配内存。在这种方法中,不会从池中删除对象,这意味着为当前未使用的东西分配内存。
这两种方法都有缺点,根据您的需求选择一种。如果有些事情不清楚,示例应该让这一切更容易理解:
示例:
您的应用程序在同一屏幕上的多个地方使用相同的图像。您启动应用程序,然后......它因OutOfMemoryError
而崩溃。图像已经压缩了,它们不能更小。你得想办法节省更多内存。
这是一个完美的用例,对于Flyweight
来说,有3张图片,每张大5MB,这意味着它们总共需要15MB,但使用Flyweight
它们只需要5MB。
此外,您通过URL
从网络获取图像,在这里使用Flyweight
也将节省时间并限制互联网消耗。
让我们实现它:
在Flyweight
,我们通常称创建类为Factory
。Application
将取决于Image
和ImageFactory
,因为它将使用Image
模型,但通过ImageFactory
创建它们。
data class Image(val bytes: ByteArray)
class ImageFactory {
private val cache = mutableMapOf()
private suspend fun getImage(url: String): Image =
cache[url] ?: fetchImage(url).also { image -> cache[url] = image }
}
如您所见,Flyweight
模式的实现非常简单。以下是使用它的方法:
fun main() {
val factory = ImageFactory()
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
val image = factory.get("image")
}
scope.launch {
val image = factory.get("image")
}
}
在这个确切的例子中,存在同步问题
然而,有一个陷阱:您对具有相同URL的图像提出2个请求,这不是我们想要的。发生这种情况是因为,在firstfetchImagefetchImage(url)
结束之前,另一个Coroutines试图从cache
获取URL,但由于它还不存在,它也调用fetchImage(url)
我们可以通过多种方式修复它。我将展示使用Mutex
的方式,它的工作原理与Flyweight
本身类似:
class ImageFactory {
private val cache = mutableMapOf()
private val locks = mutableMapOf()
private val lock = Mutex()
suspend fun get(url: String): Image {
val imageMutex = lock.withLock {
locks.getOrPut(url) { Mutex() }
}
val image = imageMutex.withLock {
getImage(url)
}
locks.remove(url)
return image
}
private suspend fun getImage(url: String): Image =
cache[url] ?: fetchImage(url).also { image -> cache[url] = image }
}