【问题标题】: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

【问题讨论】:

    标签: scala traits


    【解决方案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
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-09
        • 2011-10-21
        • 1970-01-01
        相关资源
        最近更新 更多