简体   繁体   English

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

[英]Force implementation of trait to provide an inner class that the trait can extend

Is it possible for a trait to impose a requirement that its implementing classes implement some inner class, which it can then extend? 特征是否可能强加要求其实现类实现某个内部类,然后可以对其进行扩展的要求? Eg 例如

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()

The reason I want this is that I've got a large number of ThingX classes which all are currently forced to implement their own equivalent of fancyFoo (and other similar methods that require a Mixin to be added to their specific Foo class). 我想ThingX的原因是,我有大量的ThingX类,所有这些类目前都被迫实现自己的fancyFoo等效fancyFoo (以及其他需要将Mixin添加到其特定Foo类的类似方法)。 I want to cut down on the boilerplate by moving fancyFoo and its friends into the BaseTrait, but I've been unable to come up with anything less-verbose than what's already there. 我想通过将fancyFoo及其朋友移入BaseTrait来减少样板,但是我无法提出比现有版本更详细的内容。


edit: 编辑:

My generalization above may have obscured the overall intent, so here's some background: 我在上面的概括可能掩盖了总体意图,所以这里有一些背景知识:

My actual use case revolves around modelling a database schema and some table join logic. 我的实际用例围绕着对数据库模式和一些表联接逻辑进行建模。 The team started moving away from Slick's "lifted" syntax and more towards raw sql, and this system popped up to help support writing raw queries. 团队开始从Slick的“提升”语法转移到原始SQL,此系统弹出以帮助支持编写原始查询。

Foo = TableReference . Foo = TableReference Each of the ThingX objects represent a particular table, and their respective reference classes contain methods to reference that table's columns. 每个ThingX对象代表一个特定的表,它们各自的引用类包含引用该表的列的方法。

SomeMixin = TableJoin , which was supposed to add join logic (ie how to reach one table from another table). SomeMixin = TableJoin ,应该添加TableJoin逻辑(即如何从另一个表访问一个表)。 The ThingX objects typically define a def direct to get a direct reference to the table (ie the start of a the FROM clause in a SQL query), a def from(someOtherRef) that creates an INNER JOIN , and a def optFrom(someOtherRef) that creates a LEFT JOIN . ThingX对象通常定义def direct获得直接引用表(即一开始就在FROM在一个SQL查询子句),一个def from(someOtherRef)创建一个INNER JOINdef optFrom(someOtherRef)创建一个LEFT JOIN Those three methods are what I was trying to abstract away into the BaseTrait . 这三种方法是我试图抽象到BaseTrait

I believe we do need to be able to provide a plain TableReference as well as provide a TableReference with TableJoin , as we have a utility for combining all of the join logic and we want to forbid references without any join logic from being passed into it. 我相信我们确实需要能够提供普通的TableReference以及通过TableReference with TableJoin ,因为我们有一个用于组合所有TableReference with TableJoin逻辑的实用程序,并且我们希望在没有传递任何TableReference with TableJoin逻辑的情况下禁止引用。 There are several usages of plain references throughout the codebase. 整个代码库中有几种普通引用的用法。

I'm hoping to define something along the lines of 我希望按照以下方式定义一些东西

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 = ???
}

I get stuck on the last four methods above, since it seems they require the seemingly-nonexistent feature I ask for in my original question, or for the TableSupport implementors to explicitly define the separate methods to create a Reference and a Reference with TableJoin , which ends up defeating the purpose of reducing boilerplate, because of the additional boilerplate of implementing those methods. 我被上面的最后四种方法困住了,因为它们似乎需要我在原始问题中要求的看似不存在的功能,或者让TableSupport实现者显式定义单独的方法来创建ReferenceReference with TableJoinReference with TableJoin ,由于实现这些方法的额外样板,最终无法达到减少样板的目的。

The solution I found was to wrap the classes rather than extend them, and to use an implicit unwrapper to let me interact with things the same was as if they had been extended. 我发现的解决方案是包装类而不是扩展它们,并使用一个隐式解包器让我与事物进行交互,就像扩展它们一样。

SomeTableRef with TableJoin becomes TableJoin[SomeTableRef] ie 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
}

Since the compiler is able to find the unwrap method for a TableJoin[T] without any imports, I can treat it the same as if it were a mixin: 由于编译器无需任何导入即可找到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`

Using this approach, I was able to implement the direct , from and optFrom methods as I had hoped. 使用这种方法,我能够实现我希望的directfromoptFrom方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM