给出特征MyTrait
:
trait MyTrait {
def doSomething = println("boo")
}
可以将其混合到extends
或with
:
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
答案 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
恕我直言,将其用作继承的方式有点尴尬。