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

Posted

技术标签:

【中文标题】如何将特征混合到实例中?【英文标题】:How to mix-in a trait to instance? 【发布时间】:2011-04-23 00:55:05 【问题描述】:

给定一个特征MyTrait:

trait MyTrait 
  def doSomething = println("boo")

它可以与extendswith混合成一个类:

class MyClass extends MyTrait

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

var o = new MyOtherClass with MyTrait
o.doSomething

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

我正在使用 Java 中的 JPA 加载对象,我想使用特征向它们添加一些功能。有可能吗?

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

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

【问题讨论】:

【参考方案1】:

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

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

Scala 中的 Mixin 组合发生在编译时。编译器可能做的是在现有对象 A 周围创建一个包装器 B,该包装器具有相同的类型,仅将所有调用转发到现有对象 A,然后将特征 T 混合到 B。但是,这没有实现。什么时候可以做到这一点值得怀疑,因为对象 A 可能是 final 类的实例,不能扩展。

总而言之,mixin 组合在现有对象实例上是不可能的。

更新:

与 Googol Shan 提出的智能解决方案相关,并将其推广到适用于任何特征,这是我所得到的。这个想法是在DynamicMixinCompanion trait 中提取常见的 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)                                                                                
                                                                                                    
                                                                                                    

【讨论】:

作为澄清的小注释:变量mOtherTrait 的实例,但不是 String 的实例。 (在编译时,implicit 在需要时将其“转换”回字符串。)您可以通过在 main 函数的末尾添加 println("m is instance of String/OtherTrait: " + m.isInstanceOf[String] + "/" + m.isInstanceOf[OtherTrait]) 来很好地看到这一点。 @axel22 如果我以这种方式理解正确,您可以在某些情况下将特征与行为(具有一些 def-s)混合。但不能混入一个也有一些价值的特质,对吧?【参考方案2】:

我通常使用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】:

我对这种用法有个想法:

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

【讨论】:

这是一个非常有用的功能,当你用implicit定义了一个方法,并在你的作用域中导入这个方法,这个方法可以帮助你将方法参数指定的对象转移到另一个对象当您需要调用后者未在前者定义的方法时,由方法返回指定。 非常好的解决方案,我喜欢。我想知道如何轻松地将其设为通用 - 可能在 :: 对象中添加一个通用参数可以使其适用于任何类型。它也可以与我们想要混合的任意特征一起工作吗? @axel22 是的,我认为它可以像我更新的答案一样通用。但我不能让它与任意特征一起工作,我是 scala 的新手。 好的,我在下面写了如何使它更通用。尽管如此,在我看来,还是无法避免为每个 trait 对象添加一些样板文件..【参考方案4】:

隐式类呢?与具有最终内部类和“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


【讨论】:

很好,但是使用您的解决方案,Traits 只能附加到使用 new 和在范围内创建的实例。有时您想将 Trait 附加到在其他地方创建的对象上,例如来自 ORM 层【参考方案5】:

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

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

我不确定返回值是什么:

var o = DBHelper.loadMyEntityFromDB(primaryKey);

但是让我们说,在我们的示例中是DBEntity。您可以使用 DBEntity 类并将其转换为扩展您的 trait 的类,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

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

【讨论】:

以上是关于如何将特征混合到实例中?的主要内容,如果未能解决你的问题,请参考以下文章

动态混合特征

如何组合不同的特征并将其提供给文本分类算法

如何将参数注入Scala中的类/特征方法

将 2 json 混合到 1 中。如何 ? (角JS)

如何将混合元素的 xml 序列映射到 go 结构?

如何实例化一个 vba 类并从 vb.net 调用一个方法?