web-dev-qa-db-fra.com

Comment mélanger un trait à un exemple?

Étant donné un trait MyTrait:

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

il peut être mélangé dans une classe avec extends ou with:

class MyClass extends MyTrait

Il peut également être mélangé lors de l’instanciation d’une nouvelle instance:

var o = new MyOtherClass with MyTrait
o.doSomething

Mais ... le trait (ou tout autre, si cela fait une différence) peut-il être ajouté à une instance existante?

Je charge des objets en utilisant JPA en Java et j'aimerais leur ajouter des fonctionnalités en utilisant des traits. Est-ce possible?

J'aimerais pouvoir mélanger un trait comme suit:

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

J'ai une idée pour cet usage:

//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
}

vous pouvez utiliser ce trait comme ci-dessous:

import MyTrait._

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

pour votre exemple de code:

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

J'espère que cela pourra vous aider.

MIS À JOUR

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
}

mais ce modèle a une restriction, vous ne pouvez pas utiliser une méthode d'assistance implicite déjà définie.

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
25
Googol Shan

Un objet d'exécution existant dans la machine virtuelle Java a une certaine taille sur le tas. Ajouter un trait à celui-ci signifierait changer sa taille sur le tas et changer sa signature.

La seule façon de procéder serait donc de procéder à une transformation au moment de la compilation.

La composition en mixtes dans Scala se produit au moment de la compilation. Ce que le compilateur pourrait faire est de créer un wrapper B autour d’un objet A existant avec le même type que celui qui transmet simplement tous les appels à l’objet A existant, puis de mélanger un trait T à B. Cela n’est cependant pas implémenté. On peut se demander quand cela serait possible, car l'objet A pourrait être une instance d'une classe finale, qui ne peut pas être étendue.

En résumé, la composition en mix n'est pas possible sur les instances d'objet existantes.

MIS À JOUR:

Relatif à la solution intelligente proposée par Googol Shan et généralisé pour qu’il fonctionne avec n’importe quel trait, c’est ce que j’ai eu. L'idée est d'extraire la fonctionnalité de mixin commune dans le trait DynamicMixinCompanion. Le client doit ensuite créer un objet compagnon étendant DynamicMixinCompanion pour chaque trait pour lequel il souhaite disposer de la fonctionnalité de mixin dynamique. Cet objet compagnon nécessite la définition de l'objet trait anonyme créé (::).

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)                                                                                
  }                                                                                                  
}                                                                                                    
21
axel22

J'avais l'habitude d'utiliser implicit pour mélanger une nouvelle méthode à un objet existant.

Voir, si j'ai un code comme ci-dessous:

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
}

et vous pouvez ensuite utiliser la méthode MyTrait avec un objet déjà existant, Test.

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

dans votre exemple, vous pouvez utiliser comme ceci:

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

Je réfléchis à une syntaxe de préfet pour définir cet 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
5
Googol Shan

Qu'en est-il d'une classe implicite? Cela me semble plus facile que dans les autres réponses avec une classe interne finale et une fonction "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

}
1
user573215

Pourquoi ne pas utiliser l'extension de ma bibliothèque de Scala? 

https://alvinalexander.com/scala/scala-2.10-implicit-class-example

Je ne sais pas quelle est la valeur de retour de:

var o = DBHelper.loadMyEntityFromDB(primaryKey);

mais disons que c'est DBEntity pour notre exemple. Vous pouvez prendre la classe DBEntity et la convertir en une classe qui étend votre trait, MyTrait.

Quelque chose comme:

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

class MyClass() extends MyTrait

// Have an implicit conversion to MyClass
implicit def dbEntityToMyClass(in: DBEntity): MyClass = 
new MyClass()

Je pense que vous pourriez aussi simplifier cela en utilisant simplement une classe implicite.

implicit class ConvertDBEntity(in: DBEntity) extends MyTrait

Je n'aime pas particulièrement la réponse acceptée ici, car elle surcharge l'opérateur :: pour mélanger un trait.

En Scala, l’opérateur :: est utilisé pour les séquences, c’est-à-dire:

val x = 1 :: 2 :: 3 :: Nil

L’utiliser comme un moyen d’héritage me semble un peu gênant, à mon humble avis.

0
Vaibhav Kumar