I'll explain this directly in my domain, because it would get confusing otherwise...
In MtG, there are replaceable events, e.g. gaining life. I want to use partial functions to do the replacement before an event executes. To do so, every event needs to be an instance of a case class.
//a replaceable event
trait Event { def execute(): Unit }
//an effect that replaces an event
type ReplacementEffect = PartialFunction[Event, Event]
//applies all effects to the event. If a replacement doesn't match, the
//event remains unchanged
def replace(event: Event, effects: ReplacementEffect*) =
effects.foldLeft(event) { case (event, effect) => effect.applyOrElse(event, identity[Event] _) }
def executeReplaced(event: Event) = replace(event, activeEffects: _*).execute()
//example: A player's life points
var _life = 20
//one instance of a replaceable event
case class GainLife(life: Int) extends Event {
def execute() = {
// ====== here goes the Interesting Game Logic
_life += life
}
}
def gainLife(life: Int) =
// ====== the place where Interesting Game Logic belongs logically
executeReplaced(GainLife(life))
//execution:
val double: ReplacementEffect = {
//gain twice as much life
case GainLife(x) => GainLife(x * 2)
}
//one effect is active
val activeEffects = List(double)
//gain six life
println(_life)
gainLife(3)
println(_life)
if I didn't have the requirement for replacements, I could condense (parts) to the following:
//a replaceable event
type Event = () => Unit
//example: A player's life points
//one instance of a replaceable event
def gainLife(life: Int) = executeReplaced { () =>
_life += life
}
What I like better about this code is that the Interesting Game Logic is nested in the gainLife method where it belongs. Is it possible to keep this more locally, even when I need case classes? The best I could come up with is this, but besides looking clumsy by itself, the GainLife
case class is private to the block, so it's just as usable as no case class at all:
executeReplaced {
case class GainLife(life: Int) extends Event {
def execute() = {
_life += life
}
}
GainLife(life)
}
Going away from my domain, the problem is basically making the command pattern nice-looking: there is one place where the command's logic belongs, and I want to declare it there. But then I have to define the command's arguments at the same place, and I struggle to make them available to code located elsewhere. If I separate the data from the logic, I can't execute the command anywhere except for where the logic is located, and it again breaks code locality.