Reputation: 13348
Consider the case where I have an interface that has multiple implementations, each of which is active in my application in a normal deployment. For a more concrete example, we can consider that these are implementations of a Notifier
interface, of which there is PushNotifier
, EmailNotifier
and SmsNotifier
.
In Java, using Spring, I inject all 3 into a class, and create a Map<NotificationType, Notifier>
that I can use to get the notifier for a certain type of notification.
I'm wondering what the best pattern is to do the same in Scala. Most things I have seen suggest pattern matching:
notificationType match {
case Push => pushNotifier.notify
case Email => emailNotifier.notify
// ...
}
But that seems to sort of defeat the purpose of DI/IoC. However, none of the major Scala DI frameworks provide a great mechanism for having multiple implementations of the same interface injected as a list (actually, I am using Spring in Scala right now - but trying to avoid most of the crazy functionality and use it just for basic wiring)
Is there a more Scala-esque pattern for this that I'm not grasping?
Upvotes: 2
Views: 1723
Reputation: 39577
This is not framework- (or lack thereof) dependent, but one Scalish move would be to inject the association as a function instead of as a Map. A Scala Map is also a function.
Given boring Type tokens and instances of T to associate:
scala> object Types extends Enumeration { val AType, BType, CType = Value }
defined object Types
scala> import Types._
import Types._
scala> trait T { def t: String }
defined trait T
scala> case class A(t: String = "a") extends T
defined class A
scala> case class B(t: String = "b") extends T
defined class B
scala> case class C(t: String = "c") extends T
defined class C
And an app with a function that uses it:
scala> trait AnApp { val types: Value => T ; def f(t: Value) = types(t).t }
defined trait AnApp
Then inject it as a Map:
scala> object MyApp extends AnApp { val types = Map(AType -> A("a1"), BType -> B(), CType -> C()) }
defined object MyApp
scala> MyApp f BType
res0: String = b
or a pattern matching anonymous function:
scala> object AnotherApp extends AnApp { val types: Value => T = {
| case AType => A("a2") case BType => B() case CType => C() } }
defined object AnotherApp
scala> AnotherApp f CType
res1: String = c
actually it's more convenient to use def
:
scala> trait AnApp { def types: Types.Value => T ; def f(t: Types.Value) = types(t).t }
defined trait AnApp
scala> object AnyApp extends AnApp { def types = {
| case AType => A("a2") case BType => B() case CType => C() } }
defined object AnyApp
You don't get the type inference with a val, but I seem to remember they wanted to add that.
Upvotes: 1
Reputation: 63062
Have you looked at Guice? It works well in scala too. You could wire up Modules matching your use cases. Notice in the following code (Scala not xml!) that you can define which interface is used for which module.
https://github.com/codingwell/scala-guice/
here is an example from scala-guice: in your case you would change the CreditCardPaymentService to a series of NotificationService's. You would put a single "bind" for each of your notification type's
class MyModule extends AbstractModule with ScalaModule {
def configure {
bind[Service].to[ServiceImpl].in[Singleton]
bind[CreditCardPaymentService]
bind[Bar[Foo]].to[FooBarImpl]
bind[PaymentService].to[CreditCardPaymentService]
}
}
Upvotes: 0