I need a smart mechanism for component composition which allows mixed in traits to initialize after the composed component. The following throws a NullPointerException
:
class Component {
def addListener(pf: PartialFunction[Any, Unit]) {}
}
trait DynamicComponent {
protected def component: Component
component.addListener {
case x =>
}
}
class Foo extends DynamicComponent {
protected val component = new Component
}
new Foo // -> NullPointerException
The following things are not options for me:
protected lazy val component
; that would produce an avalange of dozens of vals needing to become lazy, something I do not want.addListener
in a method, e.g. initDynamic()
; because I will be mixing in many traits, and I don't want to remember to call half a dozen initFoo()
methods.DelayedInit
. This doesn't work with traits, at least according to the scaladocs.I could live with a single init()
call, but only under the following conditions:
init()
statement.You can delay the initialization of a trait by by using early definitions. (See section 5.1.6 of the scala language specification)
class Foo extends {
protected val component = new Component
} with DynamicComponent
It's even clunkier than your solution, but you can always require the creation of a val that must be set with the init()
method. You could choose to not do it last and get an error at runtime, but at least you won't forget it entirely:
class Component {
def addListener(pf: PartialFunction[Any, Unit]) {
println("Added")
}
}
trait Dyn {
protected def component: Component
protected val initialized: Init
class Init private () {}
private object Init { def apply() = new Init() }
def init() = { component.addListener{ case x => }; Init() }
}
class Foo extends Dyn {
protected val component = new Component
protected val initialized = init()
}
No cheating!:
> class Bar extends Dyn { protected val component = new Component }
<console>:12: error: class Bar needs to be abstract, since value
initialized in trait Dyn of type Bar.this.Init is not defined
class Bar extends Dyn { protected val component = new Component }
The advantage this has is if you need multiple things to be in place before you initialize all of them cooperatively, or if your Component
class is final
so you can't mix in anything else.
AN idea could be to use the trick described here: Cake pattern: how to get all objects of type UserService provided by components
All your components that should be initialized could be registered in some Seq[InitializableComponent]
. And then you could initialize all registered components with a foreach.
No component will be forgotten in that Seq because they are registered automatically, but you can still forget to call the foreach anyway...
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