重たい処理の結果をキャッシュして、無駄な処理を行わないようにする実装パターン。GoF による 23 のデザインパターンでは、Flyweight パターンと呼ばれている。
上記 Wikipedia に Java での実装例があるが、これを Scala で実装してみる。
このパターンを使うメリットは、
- オブジェクトの生成結果をキャッシュするため、オブジェクトの生成コストが下がる
既に生成されているものは再利用、生成されていなければ生成。 - 余計なインスタンスをバコバコ作らないため、エコ
というところ。注意点としては、
- キャッシュするオブジェクトはイミュータブル(immutable)であること
キャッシュされているオブジェクトはいろいろなオブジェクトで共有されることが想定される。変更されないようにしておく。 - 増え続けるキャッシュをどうするか
キャッシュされたオブジェクトは GC の対象にならず、VM が生きている限りメモリに残り続ける。何をキャッシュするのか、しっかり考える必要がある。
Flyweight パターンの登場人物は、
- Flyweight
- FlyweightFactory
FlyweightFactory#getFlyweight
- Client
の 3 者となり、以下の例では、Flyweight -> Stamp、FlyweightFactory -> StampFactory、Client -> FlyweightPatternExample となる。
import scala.collection.mutable
// Flyweight
class Stamp(t: Char) {
def print = System.out.print(t)
}
// FlyweightFactory
object StampFactory {
val pool = mutable.Map.empty[Char, Stamp]
def get(t: Char): Stamp = pool.get(t) match {
case Some(s: Stamp) => s
case None =>
val stamp = createStamp(t)
pool += (t -> stamp)
stamp
}
private def createStamp(t: Char): Stamp = {
Thread.sleep(1000) // 実は重たい処理。。
new Stamp(t)
}
}
// Client
object FlyweightPatternExample {
def main(args: Array[String]) = {
val stamps = mutable.ArrayBuffer.empty[Stamp]
val chars = "たかいたけたてかけた".toCharArray
println("-" * 5 + " collecting ...")
chars.foreach { c =>
val start = System.nanoTime
stamps += StampFactory.get(c)
val end = System.nanoTime
println("'%s' (%d)".format(c, end - start))
}
println("-" * 5 + " output")
stamps.foreach { e =>
e.print
println(" " + e.toString)
}
}
}
実行結果は以下の通り。
[info] Running FlyweightPatternExample
----- collecting ...
'た' (1002272250)
'か' (1000112222)
'い' (1000181113)
'た' (73671)
'け' (1000181459)
'た' (8248)
'て' (1000120234)
'か' (7799)
'け' (5361)
'た' (5243)
----- output
た Stamp@11c537f
か Stamp@adcae8
い Stamp@1e4605c
た Stamp@11c537f
け Stamp@727896
た Stamp@11c537f
て Stamp@14ace71
か Stamp@adcae8
け Stamp@727896
た Stamp@11c537f
上記の結果から、
- オブジェクトの取得(
StampFactory.get
)は 10 回行われているが、生成されたインスタンスは 5 つ。 - 同じオブジェクトの 2 回目以降の取得コストはキャッシュから取得しているため、軽くなっている。
という点がわかる。