将生命周期依赖编码为特征

Encoding a lifecycle dependency into a trait

我正在用 Scala 为 SpongeAPI 写一个插件。

有几个 "constants" 在插件的某个生命周期事件被触发之前无法初始化,因为系统还没有准备好。

我在想我想把这个要求编码为一个特征,但是 Scala 没有特征参数,我也不需要在构建特征(局部参数)后保留事件需要证明它在建造时存在。

我查看了 Whosebug 以找到隐式采用该字段的 val 和 def 的建议,但这也意味着在 class 的整个生命周期中捕获事件,可能会导致泄漏参考其他 class 需要被垃圾收集的元素。

有什么建议吗?

编辑:我想象中的语法如下所示。

//Imaginary
class LateConstants extends ImaginaryTrait[GameStartingServerEvent] {
  //this class is now impossible to instantiate without a GameStartingServerEvent
  //Possibly using a factory method or something in a companion class?
  val wool = BlockTypes.WOOL 
}

但我意识到这可能是不可能的。所以我正在探索可能的解决方案:

package au.id.rleach.common

import org.spongepowered.api.block.BlockTypes
import org.spongepowered.api.command.CommandManager
import org.spongepowered.api.command.spec.CommandSpec
import org.spongepowered.api.event.game.state.GameStartingServerEvent
import org.spongepowered.api.text.Text

trait CommandRegistration {
  protected[this] implicit def e: GameStartingServerEvent
}

class constants(val e: GameStartingServerEvent) extends CommandRegistration {
  //Good, but now contains a refernece to e forever.
}

trait CommandRegistration2 {
  def registerCommands(gameStartingServerEvent: GameStartingServerEvent)
}

class constants2 extends CommandRegistration2{
  override def registerCommands(gameStartingServerEvent: GameStartingServerEvent): Unit = {
    //Better, but now the class has to run a method after construction in order to initialize correctly,
    //seems side effecty.
  }
}

class constants3(implicit gameStartingServerEvent: GameStartingServerEvent){
  //Even better, constructor doesn't need the parameter passed explicitly, can only be instantiated in the right context.
  //No trait, can't share this functionality across classes :( What about orthgonality?
}


class constants4 private (commandManager: CommandManager, basePlugin: BasePlugin){
  val exampleSpec = CommandSpec.builder().description(Text.of("example")).build()
  val exampleMapping = commandManager.register(basePlugin, exampleSpec, "example")
}

trait CommandRegistration4 {
  def registerCommands(commandManager: CommandManager)(implicit gameStartingServerEvent: GameStartingServerEvent, plugin: BasePlugin)
}

object constants4 extends CommandRegistration4{
  def registerCommands(commandManager: CommandManager)(implicit gameStartingServerEvent: GameStartingServerEvent, plugin: BasePlugin): constants4 = {
    new constants4(commandManager, plugin)
  }
}


trait LifecycleDependency[EVENT, RETURN] {
  def initialize(implicit event: EVENT, plugin: BasePlugin):RETURN
}

trait CommandRegistration5 extends LifecycleDependency[GameStartingServerEvent, constants5]

object constants5 extends CommandRegistration5 {
  override def initialize(implicit event: GameStartingServerEvent, plugin: BasePlugin): constants5 = {
    new constants5(plugin)
  }
}

class constants5 private (plugin: BasePlugin){
  val wool = BlockTypes.WOOL //only valid after GameStartingServerEvent
  val obsidian = BlockTypes.OBSIDIAN
} //No nice easy way to inherit the constraints of the trait, requires all the bolierplate above.


class Constants6{
  val wool = BlockTypes.WOOL
}

object Constants6{
  def apply()(implicit gameStartingServerEvent: GameStartingServerEvent)={
    new Constants6
  }
}

object test{
  def test()={
    Constants6()//Not bad, but requires the above in every implementation.
  }
}

编辑:SpongeAPI 的一个示例插件,使代码调用的位置和方式更加清晰,SpongeAPI 不在我的开发控制范围内,因此无法更改事件。

@Plugin(id = "au.id.rleach.memorystones", name = "MemoryStones", version = "0.0.1", dependencies = "required-after:au.id.rleach.multiblocks@[0.0.1]")
class ExamplePlugin {

  //ExamplePlugin gets instantiated external to my plugin using Guice from a primarily Java environment.


  @Listener
  def onGameReadyForListeners(event: GameStartingServerEvent) = {
    /**Annotated method that gets called with the event when the game is
       ready and the constants can be accessed safely. I could just access
       everything I need from this point onwards, however I was hoping to 
       encode it in the type system so I was unable to make the error of
       referencing anything too soon. It's also possible to register other
       classes to listen for events by listening for this event (or an
       earlier preinitialization event and registering that class instance
       with the API.
    **/
    val late: LateConstants = new LateConstants(event)
    val somethingThatReliesOnLateConstants = new Something(late)
  }
}

此示例要求每个游戏创建游戏事件(不是全局 - 见下文):

scala> trait GameStartingEvent {
     |   println("GAME STARTED EVENT!")
     | }
defined trait GameStartingEvent

scala> trait AfterGameStarted {
     |   val collectEvidence = gameStartedEvidence
     |
     |   println("START OF THE POST GAME")
     |
     |   object gameStartedEvidence extends GameStartingEvent
     |
     |   println("END OF THE POST GAME")
     | }
defined trait AfterGameStarted

scala> val after = new AfterGameStarted {}
GAME STARTED EVENT!
START OF THE POST GAME
END OF THE POST GAME
after: AfterGameStarted = $anon@6493d8f

scala> trait GameOne extends AfterGameStarted
defined trait GameOne

scala> val game1 = new GameOne {}
GAME STARTED EVENT!
START OF THE POST GAME
END OF THE POST GAME
game1: GameOne = $anon@3e26dada

scala> trait GameTwo
defined trait GameTwo

scala> val game2 = new GameTwo with AfterGameStarted {}
GAME STARTED EVENT!
START OF THE POST GAME
END OF THE POST GAME
game2: GameTwo with AfterGameStarted = $anon@136c834

scala> val game3 = new AfterGameStarted with GameTwo {}
GAME STARTED EVENT!
START OF THE POST GAME
END OF THE POST GAME
game3: AfterGameStarted with GameTwo = $anon@404043cb

Scala 在某些方面是惰性的,所以当我们在 trait AfterGameStarted 中创建一个对象并初始化该 trait 时,对象仍然没有被创建。我们必须在特征构造函数中使用 val collectEvidence ... 显式引用它。

后面的示例表明,这适用于继承和混合,而无需重复样板代码。

我仍然认为这不是解决问题的最佳方法,如果您在某些情况下不小心,它可能会崩溃。

如果您需要初始化单个全局事件,那就更简单了:

scala> object GlobalGameStartingEvent {
     |   println("GAME STARTED EVENT!")
     | }
defined object GlobalGameStartingEvent

scala> trait AfterGameStarted {
     |   val ensureGameStarted = GlobalGameStartingEvent
     |
     |   println("START/END OF THE POST GAME")
     | }
defined trait AfterGameStarted

scala> val after = new AfterGameStarted {}
GAME STARTED EVENT!
START/END OF THE POST GAME
after: AfterGameStarted = $anon@65058509

scala> val after = new AfterGameStarted {}
START/END OF THE POST GAME

值得提及DelayedInithttp://www.scala-lang.org/files/archive/nightly/docs/library/index.html#scala.DelayedInit,已弃用:

 trait GameStartingEvent extends DelayedInit {
   override def delayedInit(body: => Unit) = {
     println("GAME STARTED EVENT!")
     body
   }
 }

 class AfterGameStarted extends GameStartingEvent {    
    println("START/END OF THE POST GAME")
 }
 after: AfterGameStarted = $anon@1e8516be

scala> val after = new AfterGameStarted {}
GAME STARTED EVENT!
START/END OF THE POST GAME
after: AfterGameStarted = $anon@3838ad7

来自文档:

Classes and objects (but note, not traits) inheriting the DelayedInit marker trait will have their initialization code rewritten as follows: code becomes delayedInit(code).