0

I'm willing how to implement an extensible dispatch mechanism in Scala.

For example:

I have a trait called Sender (with a method 'send') and a bunch of classes that implement that trait (MailSender, IPhoneSender, AndroidSender). On top of them there is a class which implements the same trait but dispatches the message to the above senders depending the type of the message.

I know I can use pattern matching, but my problem with that approach is about extensibility: If someone wants to add another sender (i.e. WindowsPhoneSender), he must add a new case to the pattern matching method (thus breaking the open-closed principle). I don't want developers to modify the library's code, so I need this to be as extensible as possible.

I thought about a chain of responsibility approach (in Java I would do that), but is there a better way in Scala? (My knowledge in Scala is limited, but I know the Scala compiler does a lot of magical things)

Thanks!

  • why not use a pub-sub pattern? should the message only be dispatched to one sender? – Kim Stebel Mar 06 '15 at 14:36
  • No. The sender method recieves a Message and a list of Recipients (a recipient can be an IPhone, a Mail address, ...). It will iterate through the list of recipients and dispatch the message to the correct sender. – Santiago Ignacio Poli Mar 06 '15 at 14:41

1 Answers1

1

It would be clearer if you gave a more concrete use case, but you might be looking for the typeclass pattern:

case class AndoidMessage()
case class WindowsMessage()

trait Sender[M]{
  def send(message: M)
}

implicit object AndroidSender extends Sender[AndroidMessage]{...}
implicit object WindowsSender extends Sender[AndroidMessage]{...}

def mySendMethod[M: Sender](message: M) = {
  // use the implicit Sender[M] to send the message
}
//AndroidSender is resolved implicitly
mySendMethod(new AndroidMessage())

//third party can define their own message and their own
//implicit sender for it (perhaps in a companion object
//so it's resolved automatically)
case class BeosMessage()
object BeosMessage{
  implicit object BMSender extends Sender[BeosMessage]{...}
}
lmm
  • 17,386
  • 3
  • 26
  • 37
  • Ah you beat me. I was just about to post this; I think it's exactly what he wants. – Jonathan Crosmer Mar 06 '15 at 15:20
  • This solution was great after I realized that the people using my code must know the available senders beforehand. They must import all the Senders for them to be available in the implicit scope. If I add a new class 'BlackBerrySender' with its implicit object, I need to add an import in the class that is using it. Maybe I'm wrong, can someone clarify? – Santiago Ignacio Poli Mar 10 '15 at 18:19
  • Adding the implicit object inside the Message object works without importing, but I think it's not the responsibility of the message to know how to be sent (the Sender must have the 'send' logic, not the message). – Santiago Ignacio Poli Mar 10 '15 at 18:28
  • If you have something that handles a generic message-and-sender you pass it through as a context bound: `def processGenericMessage[M: Sender](message: M) = ...`. Things which handle specific messages know about the specific `Sender` instance for that message. – lmm Mar 10 '15 at 19:55
  • Can you please explain that approach more clearly? Thanks for your patience – Santiago Ignacio Poli Mar 11 '15 at 20:36
  • I don't know what to say to make things clearer - maybe you could give an explicit example of what you want to do? – lmm Mar 12 '15 at 09:30
  • What's the body for **def processGenericMessage[M: Sender](message: M)**? I can't send a message within that method (i can't obviously do M.send(message)) because I don't have a reference to the sender. – Santiago Ignacio Poli Mar 12 '15 at 12:57
  • The behaviour I'm trying to do is simple: A 'Dispatcher' class with a method that receives a Message and a list of Recipients (a recipient can be an iPhone, an Android, a Mail Account). It then must iterate through the list of recipients and dispatch the message to the correct sender. But I want it to be extensible (i.e. the capability to add a WindowsPhoneSender without touching the 'Dispatcher' code). In Java I'll do a chain of responsibility: Simply adding a new element to the chain that handles a specific type of Recipient will do. But I think Scala has better ways to do that. – Santiago Ignacio Poli Mar 12 '15 at 13:22
  • For the sake of the example, let's assume that the Message is a String. – Santiago Ignacio Poli Mar 12 '15 at 13:22
  • Oh that syntax means the sender is passed implicitly. So you can do `def processGenericMessage[M: Sender](message: M) = implicitly[Sender[M]].send(message)`. (In that particular case you'd probably pass the implicit explicitly, `def processGenericMessage[M](message: M)(implicit sender: Sender[M]) = sender.send(message)` - but the `M: Sender` syntax is useful if you just want to call another method that requires the `Sender`). – lmm Mar 13 '15 at 10:22
  • To be able to pass a list of `Recipient: Sender` (I think the `Sender` is associated with a particular type of recipient, not a particular type of message?) requires something more complex, probably using `HList`. See http://stackoverflow.com/questions/22269325/find-type-class-instances-for-shapeless-hlist – lmm Mar 13 '15 at 10:25
  • Yeah, the message is the same, what changes is the recipient. Thank you very much. I'll read a lot more about Type Classes and the Shapeless library. – Santiago Ignacio Poli Mar 13 '15 at 14:17