I'm trying to design a small module system for my application such that I can do this:
new MyApplication extends Module1 with Module2 ... with ModuleN
In order to let my modules register themselves with the application, I also have:
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 {
...
}
The idea is that modules can register a function with the registry to be called when the application starts up. Unfortunately, the Scala compiler forces me to do this:
trait Module1 extends Module {
self: ModuleRegistry =>
}
meaning that all implementations of a module have to explicitly self-type themselves with the registry, when ideally they'd have no knowledge of it.
So my questions are:
This isn't a good use of the cake pattern, as you want all the modules to retain their own functionality (presumably), not mix and overwrite each other when mixed in. If you want the latter, you shouldn't: only with very careful design can this give predictable and sane results, and if third parties are supposed to provide the modules it's pretty much guaranteed that the design will not be that careful.
Instead, you should adhere to the "favor composition over inheritance" advice.
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"
This is assuming a singleton is okay for a module. If you need a specific instance, you can either override apply (syntax is then Module1()) in the companion object, or add a builder trait that Module1 extends (e.g. trait ModuleBuilder { def module: Module }
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With