我们正在重构继承的method
以使用类型类 - 我们希望将所有method
实现集中在一个地方,因为将它们分散在实现类中会使维护变得困难。但是,我们遇到了一些麻烦,因为我们对类型类很新。目前method
定义为
trait MethodTrait {
def method: Map[String, Any] = // default implementation
}
abstract class SuperClass extends MethodTrait {
override def method = super.method ++ // SuperClass implementation
}
class Clazz extends SuperClass {
override def method = super.method ++ // Clazz implementation
}
等等,总共有50多个具体类,层次结构相当浅(abstract class SuperClass
- > abstract class SubSuperClass
- > abstract class SubSubSuperClass
- > {{ 1}}就像它一样深,并且具体的类永远不会扩展另一个具体的类。 (在实际实施中,class ConcreteClass
会返回Play框架method
而不是JsObject
。)我们尝试将其替换为类型类:
Map[String, Any]
我遇到两个问题:
A 即可。模仿先前实现中的trait MethodTrait[T] {
def method(target: T): Map[String, Any]
}
class MethodType {
type M[T] = MethodTrait[T]
}
implicit object Clazz1Method extends MethodTrait[Clazz1] {
def method(target: Clazz1): Map[String, Any] { ... }
}
implicit object Clazz2Method extends MethodTrait[Clazz2] {
def method(target: Clazz2): Map[String, Any] { ... }
}
// and so on
功能。目前我正在使用
super.method ++
但是这很难看,如果我不小心将方法调用到层次结构太远,我就不会收到警告或错误,例如如果class Clazz1 extends SuperClass
class Clazz2 extends SubSuperClass
private def superClassMethod(s: SuperClass): Map[String, Any] = { ... }
private def subSuperClassMethod(s: SubSuperClass): Map[String, Any] = {
superClassMethod(s) ++ ...
}
implicit object Clazz1Method extends MethodTrait[Clazz1] {
def method(target: Clazz1): Map[String, Any] = {
superClassMethod(target) ++ ...
}
}
implicit object Clazz2Method extends MethodTrait[Clazz2] {
def method(target: Clazz2): Map[String, Any] = {
subSuperClassMethod(target) ++ ...
}
}
拨打Clazz2
而不是superClassMethod
。
乙即可。在超类上调用subSuperClassMethod
,例如
method
理想情况下,我希望能够告诉编译器val s: SuperClass = new Clazz1()
s.method
的每个子类在类型类中都有SuperClass
的对应隐式对象,因此method
是类型安全的(或者,如果我忽略了为s.method
的子类实现相应的隐式对象,我将得到编译时错误,但是我已经能够提出的是
SuperClass
这是丑陋的,如果我省略了一个类,就不会给我编译时警告或错误,因为我无法将implicit object SuperClassMethod extends MethodTrait[SuperClass] {
def method(target: SuperClass): Map[String, Any] = {
target match {
case c: Clazz1 => c.method
case c: Clazz2 => c.method
...
}
}
}
定义为密封特征。
我们愿意接受类型类的替代方法,这样我们就可以将SuperClass
代码集中在一个地方。 method
仅在两个地方被调用:
一个。其他method
实施,例如method
有Clazz1
,在这种情况下,val clazz2: Option[Clazz2]
中的method
实施就像
Clazz1
B中。顶级Play Framework控制器(即所有控制器继承的抽象类),我们在其中定义了一个调用def method = super.method ++ /* Clazz1 method implementation */ ++
clazz2.map(_.method).getOrElse(Map())
的三个ActionBuilders
,例如
method
答案 0 :(得分:6)
我认为类型类与您的场景不兼容。当类型不相交时,它们很有用,但实际上要求实例反映超类型/子类型层次结构并且不是独立的。
通过这种重构,您只是创造了挑选错误实例的危险:
trait Foo
case class Bar() extends Foo
trait HasBaz[A] { def baz: Set[Any] }
implicit object FooHasBaz extends HasBaz[Foo] { def baz = Set("foo") }
implicit object BarHasBaz extends HasBaz[Bar] { def baz = FooHasBaz.baz + "bar" }
def test[A <: Foo](x: A)(implicit hb: HasBaz[A]): Set[Any] = hb.baz
val bar: Foo = Bar()
test(bar) // boom!
所以你最终用SuperClassMethod
中的模式匹配器重写了多态分派。你基本上去OO - &gt; FP - &gt; OO,同时使类型类的想法无法使用(待打开),而是以和类型结束(已知所有子类型)。
答案 1 :(得分:2)
@ 0__正在处理 - 在编译时出现隐式解析,因此用于给定输入的类型类实例将不取决于该输入的运行时类型。
要获得所需的行为,您需要编写一些隐式定义,这些定义将反映您要在其上调用method
以选择正确的类型类实例的对象的实际类型。
我认为这更像是一个维护问题而不是你现在所拥有的问题。
答案 2 :(得分:2)
简单地说:如果您想在一个地方实施您的实施,您应该为您的层次结构使用案例类:
abstract class SuperClass;
case class Clazz(...) extends SuperClass;
def method(o : SuperClass) : Map[String, Any] = o match {
case Clazz( ... ) => defaultMethod ++ ...
case ...
}
(注意方法当然可以是递归的) 因为你可以在scala中使用打开 Sum类型(编译器不会警告缺少模式),这应该可以解决你的问题,而不必滥用类型类。