2010-10-08 27 views
43

dado un rasgo MyTrait:¿Cómo mezclar un rasgo con una instancia?

trait MyTrait { 
    def doSomething = println("boo") 
} 

se puede mezclar en una clase con extends o with:

class MyClass extends MyTrait 

También se puede mezclar a una instancia de una nueva instancia:

var o = new MyOtherClass with MyTrait 
o.doSomething 

Pero ... puede el rasgo (o cualquier otro si eso hace la diferencia) ser agregado a un ins existente tance?

Estoy cargando objetos usando JPA en Java y me gustaría agregarles algunas funcionalidades usando rasgos. ¿Es posible?

me gustaría ser capaz de mezclar en un rasgo de la siguiente manera:

var o = DBHelper.loadMyEntityFromDB(primaryKey); 
o = o with MyTrait //adding trait here, rather than during construction 
o.doSomething 

Respuesta

25

Tengo una idea para este uso:

//if I had a class like this 
final class Test { 
    def f = println("foo") 
} 
trait MyTrait { 
    def doSomething = { 
    println("boo") 
    } 
} 
object MyTrait { 
    implicit def innerObj(o:MixTest) = o.obj 

    def ::(o:Test) = new MixTest(o) 
    final class MixTest private[MyTrait](val obj:Test) extends MyTrait 
} 

puede utilizar este rasgo de la siguiente manera:

import MyTrait._ 

val a = new Test 
val b = a :: MyTrait 
b.doSomething 
b.f 

para su ejemplo de código:

val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait 
o.doSomething 

I Espero que esto le pueda ayudar.

ACTUALIZADO

object AnyTrait { 
    implicit def innerObj[T](o: MixTest[T]):T = o.obj 

    def ::[T](o: T) = new MixTest(o) 
    final class MixTest[T] private[AnyTrait](val obj: T) extends MyTrait 
} 

pero este patrón tiene algunos restringen, no se puede utilizar algún método de ayuda implícita de que ya se ha definido.

val a = new Test 
a.f 
val b = a :: AnyTrait 
b.f1 
b.f 
val c = "say hello to %s" :: AnyTrait 
println(c.intern) // you can invoke String's method 
println(c.format("MyTrait")) //WRONG. you can't invoke StringLike's method, though there defined a implicit method in Predef can transform String to StringLike, but implicit restrict one level transform, you can't transform MixTest to String then to StringLike. 
c.f1 
val d = 1 :: AnyTrait 
println(d.toLong) 
d.toHexString // WRONG, the same as above 
d.f1 
+2

Esta es una característica muy útil, cuando definiste un método con 'implícito', e importas este método en tu alcance, este método puede ayudarte a transferir el objeto que especifica por el argumento del método a otro objeto que especifica por el método return cuando necesita invocar el método de este último que no está definido en el primero. –

+2

Muy buena solución, me gusta. Me pregunto cuán fácilmente podría hacerse genérico, probablemente agregar un parámetro genérico a '::' en el objeto 'MyTrait' podría permitir que funcione para cualquier tipo. ¿Podría también hacerse funcionar con rasgos arbitrarios que queremos mezclar ...? – axel22

+0

@ axel22 sí, creo que se puede hacer genérico como mi respuesta actualizada. pero no puedo hacerlo para trabajar con rasgo arbitrario, soy un novato para scala. –

20

Un objeto de tiempo de ejecución existente en la JVM tiene un cierto tamaño en el montón. Agregarle un rasgo significaría alterar su tamaño en el montón y cambiar su firma.

Así que la única manera de hacerlo sería hacer algún tipo de transformación en tiempo de compilación.

La composición de Mixin en Scala se produce en tiempo de compilación. Lo que el compilador podría hacer potencialmente es crear un contenedor B alrededor de un objeto existente A con el mismo tipo que simplemente reenvía todas las llamadas al objeto existente A, y luego mezclar un rasgo T con B. Esto, sin embargo, no se implementa. Es cuestionable cuando esto sea posible, ya que el objeto A podría ser una instancia de una clase final, que no se puede extender.

En resumen, la composición mixin no es posible en instancias de objetos existentes.

Actualizado:

relacionados con la solución inteligente propuesto por Googol Shan, y generalizar para que funcione con cualquier rasgo, esto es por lo que yo tengo. La idea es extraer la funcionalidad mixin común en el rasgo DynamicMixinCompanion. El cliente debe entonces crear un objeto complementario que extienda DynamicMixinCompanion para cada característica para la que desee tener la funcionalidad dynamic mixin. Este objeto complementario requiere la definición del objeto de rasgo anónimo que se crea (::).

trait DynamicMixinCompanion[TT] {                  
    implicit def baseObject[OT](o: Mixin[OT]): OT = o.obj            

    def ::[OT](o: OT): Mixin[OT] with TT                
    class Mixin[OT] protected[DynamicMixinCompanion](val obj: OT)          
}                          

trait OtherTrait {                     
    def traitOperation = println("any trait")               
}                          

object OtherTrait extends DynamicMixinCompanion[OtherTrait] {           
    def ::[T](o: T) = new Mixin(o) with OtherTrait              
}                          

object Main {                       
    def main(args: Array[String]) {                  
    val a = "some string"                    
    val m = a :: OtherTrait                   
    m.traitOperation                     
    println(m.length)                     
    }                         
}                          
+0

Solo como observación menor para aclarar: la variable 'm' es una instancia de' OtherTrait' pero _not_ una instancia de 'String'. (Es el "implícito" que "convierte" de nuevo a una cadena siempre que sea necesario, en tiempo de compilación). Puede ver esto muy bien agregando 'println (" m es una instancia de String/OtherTrait: "+ m.isInstanceOf [String] ] + "/" + m.isInstanceOf [OtherTrait]) 'al final de la función' main'. – Hbf

+0

@ axel22 si entiendo correctamente de esta manera, puede mezclar en algún caso un rasgo con comportamiento (que tiene algunas def-s). Pero no se puede mezclar en un rasgo que también tiene algunos valores, ¿verdad? –

5

por lo general utilizan un implicit para mezclar en un nuevo método para un objeto existente.

Véase, si tengo algo de código de la siguiente manera:

final class Test { 
    def f = "Just a Test" 
    ...some other method 
} 
trait MyTrait { 
    def doSomething = { 
    println("boo") 
    } 
} 
object HelperObject { 
    implicit def innerObj(o:MixTest) = o.obj 

    def mixWith(o:Test) = new MixTest(o) 
    final class MixTest private[HelperObject](obj:Test) extends MyTrait 
} 

y luego se puede utilizar MyTrait método de prueba con un objeto ya existente.

val a = new Test 
import HelperObject._ 
val b = HelperObject.mixWith(a) 
println(b.f) 
b.doSomething 

en su ejemplo, puede utilizar la siguiente manera:

import HelperObject._ 
val o = mixWith(DBHelper.loadMyEntityFromDB(primaryKey)); 
o.doSomething 

Me refiero a cabo una sintaxis prefecto para definir esta HelperObject:

trait MyTrait { 
    ..some method 
} 
object MyTrait { 
    implicit def innerObj(o:MixTest) = o.obj 

    def ::(o:Test) = new MixTest(o) 
    final class MixTest private[MyTrait](obj:Test) extends MyTrait 
} 
//then you can use it 
val a = new Test 
val b = a :: MyTrait 
b.doSomething 
b.f 
// for your example 
val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait 
o.doSomething 
1

¿Qué hay de una clase implícita? Me parece más fácil en comparación con la forma en las otras respuestas con una clase interna final y una función "mixin".

trait MyTrait { 

    def traitFunction = println("trait function executed") 

} 

class MyClass { 

    /** 
    * This inner class must be in scope wherever an instance of MyClass 
    * should be used as an instance of MyTrait. Depending on where you place 
    * and use the implicit class you must import it into scope with 
    * "import mypackacke.MyImplictClassLocation" or 
    * "import mypackage.MyImplicitClassLocation._" or no import at all if 
    * the implicit class is already in scope. 
    * 
    * Depending on the visibility and location of use this implicit class an 
    * be placed inside the trait to mixin, inside the instances class, 
    * inside the instances class' companion object or somewhere where you 
    * use or call the class' instance with as the trait. Probably the 
    * implicit class can even reside inside a package object. It also can be 
    * declared private to reduce visibility. It all depends on the structure 
    * of your API. 
    */ 
    implicit class MyImplicitClass(instance: MyClass) extends MyTrait 

    /** 
    * Usage 
    */ 
    new MyClass().traitFunction 

} 
+0

Es bueno, pero con su solución, los Rasgos solo pueden adjuntarse a instancias creadas con nuevo y en el alcance. Algunas veces desea adjuntar el rasgo a un objeto creado en otro lugar, p. desde una capa ORM –

Cuestiones relacionadas