How can I create a component-based message handling system with traits?
Here's a short example of what I want to do:
abstract class Message()
case class FooMessage() extends Message
case class BarMessage() extends Message
//... other messages ...
trait Component
{
def handleMessage(msg: Message):Unit
}
trait ComponentType1 extends Component
{
abstract override def handleMessage(msg: FooMessage) = {
//handle foo, pass it up the chain
super.handleMessage(msg)
}
abstract override开发者_运维问答 def handleMessage(msg: BarMessage) = {
//handle bar, pass it up the chain
super.handleMessage(msg)
}
}
//handles some other messages, also might handle the same messages as ComponentType1
trait ComponentType2 extends Component { .. }
Then, these ComponentType
s are mixed in to a class to form an object that is completely composed of modular components.
I have a bunch of different Message
s and a bunch of different Component
s.
- Not all components handle all message types.
- Multiple components can handle the same message type.
- The message cascades up through the components, even if it's handled by another component.
- A Component can handle more than one message type.
The problem is since handleMessage
is defined in Component
as accepting a Message
, when I try to specialize the msg
parameter it doesn't count as an override.
I know one possible solution to this is to declare a big handleMessage
method with a big match statement, but I'd like to define a method per message if possible.
Here's a partial function based solution. Tip to CheatEx for the idea.
trait Message
class FooMessage extends Message
class BarMessage extends Message
abstract class Component {
type CPF = PartialFunction[Message, Unit]
var pf: CPF = { case _ => }
def handleMessage(msg: Message) = pf(msg)
}
trait ComponentType1 extends Component {
val ct1pf: CPF = {
case msg: FooMessage => println("foo1")
case msg: BarMessage => println("bar1")
}
pf = ct1pf orElse pf
}
trait ComponentType2 extends Component {
val parentPf = pf
val ct2pf: CPF = {
case msg:
BarMessage => println("bar2")
parentPf(msg) // cascade
}
pf = ct2pf orElse pf
}
object component1and2 extends ComponentType1 with ComponentType2
component1and2.handleMessage(new FooMessage)
component1and2.handleMessage(new BarMessage)
prints
foo1
bar2
bar1
trait Message
class FooMessage extends Message
class BarMessage extends Message
trait Component {
def handleMessage(msg: Message) {}
def handleMessage(msg: FooMessage) {}
def handleMessage(msg: BarMessage) {}
}
trait ComponentType1 extends Component {
override def handleMessage(msg: FooMessage) = println("foo1")
override def handleMessage(msg: BarMessage) = println("bar1")
}
trait ComponentType2 extends Component {
override def handleMessage(msg: BarMessage) = println("bar2")
}
object component1and2 extends ComponentType1 with ComponentType2
component1and2.handleMessage(new FooMessage)
component1and2.handleMessage(new BarMessage)
prints
foo1
bar2
If you had a list somewhere of all the components in the system you could do
componentList.foreach(c => c.handleMessage(msg))
It would just no-op in Component for the components that didn't handle msg.
For completeness:
trait Message
class FooMessage extends Message
class BarMessage extends Message
trait Component {
def handleMessage(msg: Message) {}
}
trait ComponentType1 extends Component {
def handleMessage(msg: Message) {
msg match {
case m: FooMessage => println("foo1")
case m: BarMessage => println("bar1")
case _ => super.handleMessage(msg)
}
}
}
trait ComponentType2 extends Component {
override def handleMessage(msg: Message) {
msg match {
case m: BarMessage =>
println("bar2")
super.handleMessage(m) // cascade
case _ => super.handleMessage(msg)
}
}
}
object component1and2 extends ComponentType1 with ComponentType2
component1and2.handleMessage(new FooMessage)
component1and2.handleMessage(new BarMessage)
prints
foo
bar2
bar1
精彩评论