在Scala中,如何继承另一个trait的自身类型特征?

6

我正在尝试为我的应用程序设计一个小型模块系统,以便我可以做到这一点:

new MyApplication extends Module1 with Module2 ... with ModuleN

为了让我的模块能够在应用程序中注册自己,我还需要:
trait ModuleRegistry {
  def register(start: () => Unit) = // Stores start functions in a list
}

trait Module {
  self: ModuleRegistry =>
  self.register(start)
  def start(): Unit
}

class Application extends ModuleRegistry {
}

trait Module1 extends Module {
  ...
}

这个想法是,模块可以在注册表中注册一个函数,当应用程序启动时调用该函数。不幸的是,Scala 编译器强制我这样做:

trait Module1 extends Module {
  self: ModuleRegistry =>
}

意味着所有模块的实现都必须在注册表中明确指定自身类型,而理想情况下它们不应该知道它。
那么我的问题是:
1. 为什么编译器要求我在扩展特质时重新指定这个 self-type?理想情况下,我甚至不希望扩展特质“看到”这个类型,因为它应该由基础模块特质处理。这个“具体”的模块应该由第三方实现,所以向他们公开注册表似乎有点丑陋。 2. 在 Scala 中是否有更好的方法来实现我正在尝试实现的目标,以便可以自由地混合模块?

1
比你想象的更糟。假设Module1Module2都定义了start... - Rex Kerr
1个回答

8
这并不是cake pattern的一个好用法,因为您希望所有模块保留自己的功能(大概率如此),在混合时不要互相混写和覆盖。如果您需要后者,您不应该那样做:只有在非常仔细的设计下才能给出可预测和明智的结果,如果第三方应该提供模块,则几乎可以保证设计不会那么仔细。

相反,您应遵循“优先使用组合而非继承”的建议。

trait Module {
  def start: Unit
}

trait Modular {
  protected def moduleList: Seq[Module]
  protected def register() = moduleList.map(m => m.start _)
}

class Application(modules: Module*) extends Modular {
  protected def moduleList: Seq[Module] = modules  // Fix varargs type
  protected val registered = register()
}

object Module1 extends Module {
  def start = println("One")
}

object Module2 extends Module {
  def start = println("Two")
}

val app = new Application(Module1, Module2) {
 registered.foreach(_())
}
// Prints "One", then "Two"

假设模块使用单例是可以的。如果您需要一个特定的实例,您可以要么在伴生对象中覆盖apply方法(语法为Module1()),要么添加一个Builder trait,使Module1扩展它(例如trait ModuleBuilder { def module: Module })。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接