如何将特征混合到实例中?

时间:2010-10-08 18:27:50

标签: scala traits

给出特征MyTrait

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

可以将其混合到extendswith

的课程中
class MyClass extends MyTrait

它也可以在实例化新实例时混合:

var o = new MyOtherClass with MyTrait
o.doSomething

但是......可以将特征(或任何其他如果产生影响的特征)添加到现有实例中吗?

我在Java中使用JPA加载对象,我想使用traits为它们添加一些功能。它有可能吗?

我希望能够按如下方式混合特征:

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

5 个答案:

答案 0 :(得分:25)

我对这种用法有所了解:

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

您可以使用以下特性:

import MyTrait._

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

示例代码:

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

我希望这可以帮到你。

已更新

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
}

但是这种模式有一些限制,你不能使用已经定义的一些隐式辅助方法。

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

答案 1 :(得分:21)

JVM中的现有运行时对象在堆上具有特定大小。添加特征意味着改变它在堆上的大小,并改变其签名。

所以唯一的方法就是在编译时进行某种转换。

Scala中的Mixin组合在编译时发生。编译器可能做的是在现有对象A周围创建一个包装器B,其类型相同,只是将所有调用转发到现有对象A,然后将特征T混合到B.但是,这并没有实现。如果可能的话,这是值得怀疑的,因为对象A可能是最终类的一个实例,无法扩展。

总之,在现有对象实例上无法使用mixin组合。

更新:

与Googol Shan提出的智能解决方案相关,并将其概括为适用于任何特征,这是我所得到的。我们的想法是在DynamicMixinCompanion特征中提取常见的mixin功能。然后,客户端应该为他想要具有动态mixin功能的每个特征创建一个扩展DynamicMixinCompanion的伴随对象。此伴随对象需要定义创建的匿名特征对象(::)。

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

答案 2 :(得分:6)

我通常使用implicit将新方法混合到现有对象中。

如果我有以下代码,请参阅:

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
}

然后您可以将MyTrait方法与已存在的对象Test一起使用。

val a = new Test
import HelperObject._
val b = HelperObject.mixWith(a)
println(b.f)
b.doSomething
在您的示例中,您可以像这样使用:

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

我正在考虑一个完美的语法来定义这个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

答案 3 :(得分:1)

隐含的类怎么样?与使用最终内部类和" mixin" -function的其他答案中的方式相比,这似乎更容易。

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

}

答案 4 :(得分:0)

为什么不使用Scala扩展我的库模式?

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

我不确定返回值是什么:

var o = DBHelper.loadMyEntityFromDB(primaryKey);

但是让我们说,对于我们的示例,它是DBEntity。您可以使用DBEntity类,并将其转换为扩展特征MyTrait的类。

类似的东西:

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

class MyClass() extends MyTrait

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

我相信您也可以通过使用隐式类来简化此操作。

implicit class ConvertDBEntity(in: DBEntity) extends MyTrait

我特别不喜欢这里接受的答案,因为它会使::运算符重载以混合特征。

在Scala中,::运算符用于序列,即:

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

恕我直言,将其用作继承的方式有点尴尬。