强制执行特质以提供该特质可以扩展的内部类

时间:2018-08-30 14:32:59

标签: scala inheritance

特征是否可以强加要求其实现类实现某个内部类,然后可以对其进行扩展?例如

trait BaseTrait {
  // not actually an "abstract class", but a requirement that
  // subclasses provide a class named Foo with this constructor signature
  abstract class Foo(bar: Bar)

  def normalFoo(bar: Bar): Foo = new Foo(bar)

  // trait needs to be able to extend the Foo class implemented by the subclass.
  // this seems to be the impossible part, as far as I can tell...
  def fancyFoo(bar: Bar): Foo with SomeMixin = new Foo(bar) with SomeMixin {
    def anExtraMethod() = println("I'm an extra!")
  }
}

object ThingA extends BaseTrait {
  class Foo(bar: Bar) {
    def getThingAStuff() = println("I'm part of ThingA")
  }
}
object ThingB extends BaseTrait {
  class Foo(bar: Bar) {
    def getThingBStuff() = println("I'm part of ThingB")
  }
}

// calling `fancyFoo` on the concrete implementations should grant
// access to the specific methods in their respective `Foo` classes,
// as well as the "extra method" that the trait adds
val aFoo: ThingA.Foo with SomeMixin = ThingA.fancyFoo(bar)
aFoo.getThingAStuff()
aFoo.anExtraMethod()

val bFoo: ThingB.Foo with SomeMixin = ThingB.fancyFoo(bar)
bFoo.getThingBStuff()
bFoo.anExtraMethod()

我想要这样做的原因是,我有大量的ThingX类,所有这些类当前都被迫实现自己的等效fancyFoo(以及其他需要Mixin的类似方法)被添加到其特定的Foo类中)。我想通过将fancyFoo及其朋友移入BaseTrait来减少样板,但是我无法提出比现有版本更详细的内容。


编辑:

我上面的概括可能掩盖了总体意图,所以这里有一些背景知识:

我的实际用例围绕数据库模型和一些表联接逻辑建模。团队开始从Slick的“提升”语法转移到原始sql,并弹出该系统以帮助支持编写原始查询。

Foo = TableReference。每个ThingX对象代表一个特定的表,它们各自的引用类包含引用该表的列的方法。

SomeMixin = TableJoin,应该添加联接逻辑(即如何从另一个表访问一个表)。 ThingX对象通常定义def direct以获得对表的直接引用(即SQL查询中FROM子句的开始),即创建的def from(someOtherRef) INNER JOIN和创建def optFrom(someOtherRef)的{​​{1}}。这是我试图抽象为LEFT JOIN的三种方法。

我相信我们要做需要能够提供简单的BaseTrait并提供TableReference,因为我们有一个实用程序可以组合所有联接逻辑并且我们希望在没有任何联接逻辑的情况下禁止引用被传递到引用中。整个代码库中有几种普通引用的用法。

我希望按照...的定义

TableReference with TableJoin

我被上面的最后四种方法困住了,因为它们似乎需要我在原始问题中要求的看似不存在的功能,或者让trait TableSupport { type Reference <: TableReference trait CanMatch[Ref] { // corresponds to the `ON` part of a `JOIN` clause def matchCondition(self: Reference, other: Ref): RawSQL } def defaultAlias: String // All of the below would be implemented by the `TableSupport` trait // in terms of the `Reference` constructor and mixing in TableJoin. // But currently each table companion has to explicitly implement these. def reference(alias: String = defaultAlias): Reference = ??? def direct(alias: String = defaultAlias): Reference with TableJoin = ??? def from[Ref: CanMatch](ref: Ref, alias: String = defaultAlias): Reference with TableJoin = ??? def optFrom[Ref: CanMatch](ref: Ref, alias: String = defaultAlias): Reference with TableJoin = ??? } 实现者​​明确定义单独的方法来创建{ {1}}和TableSupport,最终由于实现这些方法的额外样板而无法实现减少样板的目的。

1 个答案:

答案 0 :(得分:0)

我发现的解决方案是包装而不是扩展这些类,并使用隐式解包器让我与事物交互就像它们具有已扩展。

SomeTableRef with TableJoin成为TableJoin[SomeTableRef],即

class TableJoin[T <: TableReference](val self: T, val joinStep: RawSQL)
object TableJoin {
  import language.implicitConversions
  implicit def unwrap[T <: TableReference](tj: TableJoin[T]): T = tj.self
}

由于编译器无需任何导入即可找到TableJoin [T]的unwrap方法,因此可以将其视为mixin:

class SomeTableRef(alias: String) extends TableReference {
  def id = column("ID")
}
val joinedRef = new TableJoin(new SomeTableRef(defaultAlias), /* raw sql */)
joinedRef.id // compiles fine because of `unwrap`

使用这种方法,我能够实现我所希望的directfromoptFrom方法。