问题
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:
- Using
protected lazy val component
; that would produce an avalange of dozens of vals needing to become lazy, something I do not want. - Putting
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 dozeninitFoo()
methods. - Using
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:
- all mixed in traits can easily declare to be invoked in this one single call
- it is a compile error to forget the
init()
statement.
回答1:
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
回答2:
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.
回答3:
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...
回答4:
Here is one idea (I am happy to read about other suggestions):
class Component {
def addListener(pf: PartialFunction[Any, Unit]) {
println("Added")
}
}
trait DynamicComponentHost {
protected def component: Component with DynamicPeer
protected trait DynamicPeer {
_: Component =>
addListener {
case x =>
}
}
}
class Foo extends DynamicComponentHost {
protected val component = new Component with DynamicPeer
}
new Foo
So basically I am forcing the component to mix in a type that can only be provided by the mixed in trait. Reasonable? Looks a bit too complicated in my eyes.
来源:https://stackoverflow.com/questions/16251379/delaying-trait-initialization