斯卡拉。有没有办法选择自我类型的超级方法实现?

时间:2016-10-13 14:58:41

标签: scala

当我扩展特征时,我可以选择使用哪种方法实现。像这里:

object Main {

  def main(args: Array[String]): Unit = {
    val c = new C
    println(c.a)
    println(c.b)
  }

  trait Parent {
    def foo: String
  }

  trait A extends Parent {
    override def foo = "from A"
  }

  trait B extends Parent {
    override def foo = "from B"
  }

  class C extends A with B {
    val b = super[A].foo
    val a = super[B].foo
  }

}

但如果我想对自我类型做同样的事情,它似乎不可能:

object Main {

  def main(args: Array[String]): Unit = {
    val c = new C with A with B
    println(c.a)
    println(c.b)
  }

  trait Parent {
    def foo: String
  }

  trait A extends Parent {
    override def foo = "from A"
  }

  trait B extends Parent {
    override def foo = "from B"
  }

  class C {
    self: A with B =>

    val b = super[A].foo
    val a = super[B].foo
  }

}

这不会编译。我是对的,不可能吗?如果我是对的,为什么会这样,并且有解决方法吗?

更新 为什么我首先需要?我正在玩依赖注入使用自我类型而不是构造函数注入。所以我有一个基本特征转换器和子特征FooConverter和BarConverter。我想这样写(当然不能奏效):

object Main {

  class Foo

  class Bar

  trait Converter[A] {
    def convert(a: A): String
  }

  trait FooConverter extends Converter[Foo] {
    override def convert(a: Foo): String = ???
  }

  trait BarConverter extends Converter[Bar] {
    override def convert(a: Bar): String = ???
  }

  class Service {

    this: Converter[Foo] with Converter[Bar] =>

    def fooBar(f: Foo, b:Bar) = {
      convert(f)
      convert(b)
    }
  }

}

我认为这是因为仿制药,但事实并非如此。所以我只是想知道是否有可能以某种方式调用自我类型选择特征的超级方法。因为简单的继承是可能的。至于我原来的问题,我可以像这样写它,它会起作用:

object Main {

  class Foo

  class Bar

  trait Converter[A] {
    def convert(a: A): String
  }

  trait FooConverter extends Converter[Foo] {
    override def convert(a: Foo): String = ???
  }

  trait BarConverter extends Converter[Bar] {
    override def convert(a: Bar): String = ???
  }

  class Service {

    this: FooConverter with BarConverter =>

    def fooBar(f: Foo, b:Bar) = {
      convert(f)
      convert(b)
    }
  }

}

可能更严格的抽象,但我不确定这种情况是否不好,如果我需要像Converter [A]那样广泛的抽象。

2 个答案:

答案 0 :(得分:2)

从已构造的类型调用super方法是不可能的(您只能从内部执行)。在您的示例中,您尝试在运行时构建的实例foo上调用self,因此foo是虚拟的并且可以被覆盖 - 编译器不会在运行时调用trait CC extends A with B { val b = super[A].foo val a = super[B].foo } class C { self: CC => } 。知道将要调用哪个实际实现(正式与实际类型问题)。所以从技术上讲 - 你不可能做你想做的事情(将虚拟方法称为静态方法)。

天真的黑客是:

a

它基本上提供了你想要的封装 - 你可能想重新定义C类中的bC因为它们不可用(在类型C本身中)直到您将CCval c混合。

请注意,在您提供的每个示例中(包括我的天真解决方案) - 结果foo无论如何都可以访问foo以及将要调用哪个A取决于您是怎么做的混合BA with BB with AC)。因此,您获得的唯一封装是foo类型本身不具有class Foo class Bar trait Converter[A] { def convert(a: A): String } object FooConverter1 extends Converter[Foo] { override def convert(a: Foo): String = ??? } object BarConverter1 extends Converter[Bar] { override def convert(a: Bar): String = ??? } trait FooBarConvertService { def fooConverter: Converter[Foo] def barConverter: Converter[Bar] def fooBar(f: Foo, b: Bar) = { fooConverter(f) barConverter(b) } } trait Converters { def fooConverter: Converter[Foo] = FooConverter1 def barConverter: Converter[Bar] = BarConverter1 } object App extends FooBarConvertService with Converters with ... 方法。这意味着自我类型为您提供了一种临时关闭(私有)方法的方法"子类"没有违反LSP - 但这不是唯一的方式(见下文)。

除此之外,一些人authors认为你尝试实施的蛋糕注射是不切实际的。您可能希望查看Thin Cake Pattern - 作为评论,我在实际项目中成功使用了类似的东西(与构造函数注入相结合)。

我会以这种方式实现您的转换器服务:

Converter[Bar]

这允许您在将所有内容组合在一起时更改/模拟转换器实现。

我还注意到Function1[Bar, String]只是Bar => Stringsealed trait FooBar //introduced it just to make types stronger, you can omit it if you prefer class Foo extends FooBar class Bar extends FooBar trait FooBarConvertService { type Converter[T <: FooBar] = T => String def fooConverter: Converter[Foo] def barConverter: Converter[Bar] def fooBar(f: Foo, b: Bar) = { fooConverter(f) barConverter(b) } } trait FooConverterProvider { def fooConverter: Foo => String = ??? } trait BarConverterProvider { def barConverter: Bar => String = ??? } object App extends FooBarConvertService with FooConverterProvider with BarConverterProvider ,所以实际上你并不需要单独的界面:

def fooConverter(f: Foo): String = ???

您也可以使用def fooConverter: Foo => String = ???代替private[package]

谈论封装 - 它在这里更加薄弱,因为你可以访问传递依赖,所以如果你真的需要它 - 使用package converters trait FooBarConvertService { type Converter[T <: FooBar] = T => String private[converters] def fooConverter: Converter[Foo] private[converters] def barConverter: Converter[Bar] def fooBar(f: Foo, b: Bar) = { fooConverter(f) barConverter(b) } } trait FooConverterProvider { private[converters] def fooConverter: Foo => String = ??? } trait BarConverterProvider { private[converters] def barConverter: Bar => String = ??? } 修饰符。

转换器模块:

package client 
import converters._  

object App 
  extends FooBarConvertService 
  with FooConverterProvider 
  with BarConverterProvider

核心模块:

object converters {...}; object client {...}

如果您愿意,可以使用对象fooConverter代替包。

这种封装甚至比基于自我类型的封装更强大,因为您无法从barConverter对象访问App / foo(在您的示例中{{1} }仍然可以从val c = new C with A with B)访问:

 client.App.fooBar(new Foo, new Bar) //OK
 client.App.fooConverter
 <console>:13: error: method fooConverter in trait FooConverterProvider cannot be accessed in object client.App
          client.App.fooConverter
                     ^

答案 1 :(得分:1)

请记住,自我类型允许您要求使用您混合的特征的任何客户端代码也必须混合使用另一个特征。换句话说,它是一种声明依赖关系的方式。但它不是经典的继承。所以,当你说C类{self:A with B =&gt;实际上A和B当时不在那里。您刚刚定义了客户端代码必须在A和B中混合才能在C中混合。

但是对于您的特定用例,似乎您可以使用此代码之类的内容实现相同的目标。换句话说,首先创建第三个特征,然后将其扩展到特定的类中。

object DoubleSelfType extends App {
      val c = new DoubleFoo
      println(c.a)
      println(c.b)

      trait Parent {
        def foo: String
      }

      trait A extends Parent {
        override def foo = "from A"
      }

      trait B extends Parent {
        override def foo = "from B"
      }

      trait C {
        self: A with B =>

        val a = ""
        val b = ""

      }

      class DoubleFoo extends C with A with B {
        override val b = super[A].foo
        override val a = super[B].foo
      }

    }