Delaying trait initialization

帅比萌擦擦* 提交于 2020-01-02 04:49:26

问题


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 dozen initFoo() 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!