特征是否可以强加要求其实现类实现某个内部类,然后可以对其进行扩展?例如
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
,最终由于实现这些方法的额外样板而无法实现减少样板的目的。
答案 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`
使用这种方法,我能够实现我所希望的direct
,from
和optFrom
方法。