Se pueden definir unos InternableN[Arg1, Arg2, ..., ResultType]
rasgos para N es el número de argumentos para apply()
: Internable1[A,Z]
, Internable2[A,B,Z]
, etc. Estos rasgos definen la caché en sí misma, el método intern()
y el método apply
que queremos secuestro.
Vamos a tener que definir un rasgo (o una clase abstracta) para asegurar que sus rasgos InternableN
que hay de hecho un método de aplicar que se debe anular, llamémoslo Applyable
.
trait Applyable1[A, Z] {
def apply(a: A): Z
}
trait Internable1[A, Z] extends Applyable1[A, Z] {
private[this] val cache = WeakHashMap[(A), Z]()
private[this] def intern(args: (A))(builder: => Z) = {
cache.getOrElse(args, {
val newObj = builder
cache(args) = newObj
newObj
})
}
abstract override def apply(arg: A) = {
println("Internable1: hijacking apply")
intern(arg) { super.apply(arg) }
}
}
El objeto compañera de su clase tiene que ser un mixin de una clase concreta implementación de ApplyableN
con InternableN
. No funcionaría tener una aplicación definida directamente en su objeto compañero.
// class with one apply arg
abstract class SomeClassCompanion extends Applyable1[Int, SomeClass] {
def apply(value: Int): SomeClass = {
println("original apply")
new SomeClass(value)
}
}
class SomeClass(val value: Int)
object SomeClass extends SomeClassCompanion with Internable1[Int, SomeClass]
Una cosa buena acerca de esto es que la aplicación original no necesita ser modificada para atender el internado. Solo crea instancias y solo se llama cuando deben crearse.
Todo el asunto puede (y debe) también definirse para las clases con más de un argumento. Para el caso de dos argumentos:
trait Applyable2[A, B, Z] {
def apply(a: A, b: B): Z
}
trait Internable2[A, B, Z] extends Applyable2[A, B, Z] {
private[this] val cache = WeakHashMap[(A, B), Z]()
private[this] def intern(args: (A, B))(builder: => Z) = {
cache.getOrElse(args, {
val newObj = builder
cache(args) = newObj
newObj
})
}
abstract override def apply(a: A, b: B) = {
println("Internable2: hijacking apply")
intern((a, b)) { super.apply(a, b) }
}
}
// class with two apply arg
abstract class AnotherClassCompanion extends Applyable2[String, String, AnotherClass] {
def apply(one: String, two: String): AnotherClass = {
println("original apply")
new AnotherClass(one, two)
}
}
class AnotherClass(val one: String, val two: String)
object AnotherClass extends AnotherClassCompanion with Internable2[String, String, AnotherClass]
La interacción muestra que la Internables' aplicar método ejecuta antes de la original de apply()
que es ejecutado sólo si es necesario.
scala> import SomeClass._
import SomeClass._
scala> SomeClass(1)
Internable1: hijacking apply
original apply
res0: SomeClass = [email protected]
scala> import AnotherClass._
import AnotherClass._
scala> AnotherClass("earthling", "greetings")
Internable2: hijacking apply
original apply
res1: AnotherClass = [email protected]
scala> AnotherClass("earthling", "greetings")
Internable2: hijacking apply
res2: AnotherClass = [email protected]
yo decidimos utilizar un WeakHashMap para que el caché de internar no impide la recolección de basura de los casos internados vez que ya no están referenciados en cualquier lugar.
Código claramente disponible as a Github gist.