dependency injection(scala)
- this
trait User { def name :String }
// :type , a trait only consisting an instance of a String trait B { somename : User => def foo() { println(name) } }
//needn't be somename.name(it must be) ,somename is an instance of trait User object Main extends B with User { override def name = "whj"; } Main.foo
trait Foo{ this => {def close:Unit} } //foo is a function
//This allows for safe mixins for duck-typing.
- how to implement/achieve dependency injection (DI) in Scala
1 Cake Pattern
This pattern is first explained in Martin Oderskys' paper scalable component abstractions as the way he and his team structured the Scala compiler.
2 dummy codes--just split the interface and its implementation
class U { def auth(u:U): U = {println("auth u"+ u) u} def create(u:U) =println("create u"+ u) def delete(u:U) =println("delete u"+ u)} class S{
def auth(uname:String, s:String ): U =uname.auth(u,s)
def create(uname:String, s:String)=u.create(new U(uname, pass)) def delete(u:U) = All is statically typed. u.delete(u)}
3 interesting parts--wrap the UserRepository
in an enclosing trait and instantiate the user repository
trait UserRepositoryComponent { val userRepository = new UserRepository class UserRepository { def authenticate(user: User): User = { println("authenticating user: " + user) user } def create(user: User) = println("creating user: " + user) def delete(user: User) = println("deleting user: " + user) } } //wrap and use a self-type annnotation to declare our need for U service //using self-type annotation declaring the dependencies this component requires trait UserServiceComponent { this: UserRepositoryComponent => val userService = new UserService class UserService { def authenticate(username: String, password: String): User = userRepository.authenticate(username, password) def create(username: String, password: String) = userRepository.create(new User(username, password)) def delete(user: User) = userRepository.delete(user) } } //this: Foo with Bar with Baz => declared the dependency
- all wiring is statically typed =>if one dependency is missing, we get a compilation error
- immutable, all dependencies are declared as val =>get the top-level component from the registry and all are wired, like Guice ,Spring
object ComponentRegistry extends UserServiceComponent with UserRepositoryComponent val userService = ComponentRegistry.userService val user = userService.authenticate(..)
4 have strong coupling between the service implementation and its creation, the wiring configuration is scattered all over our code base
Instead of instantiating the services in their enclosing component trait, change it to an abstract member field.
object ComponentRegistry extends UserServiceComponent with UserRepositoryComponent{ val userRepository = new UserRepository val userService = new UserServic }
5
trait TestEnvironment extends UserServiceComponent with UserRepositoryComponent with org.specs.mock.JMocker { val userRepository = mock(classOf[UserRepository]) val userService = mock(classOf[UserService]) }
6
class UserServiceSuite extends TestNGSuite with TestEnvironment { @Test { val groups=Array("unit") } def authenticateUser = { // create a fresh and clean (non-mock) UserService // (who's userRepository is still a mock) val userService = new UserService // record the mock invocation expect { val user = new User("test", "test") one(userRepository).authenticate(user) willReturn user } ... // test the authentication method } ... }
-
Other alternatives
主要思想,将interface用trait,implement用class,config用object
再class来以struct type为参数,struct里为所有的interface创建val,def一个method来结合所var的interface
injection发生在object Config中,Config除了lazy val所有的implement的class,还有即为injection的lazy war in = new injectionclassname(this)
- using implicit
- using Google Guice
trait ServiceInjector { ServiceInjector.inject(this) } // helper companion object object ServiceInjector { private val injector = Guice.createInjector( Array[Module](new DependencyModule)) def inject(obj: AnyRef) = injector.injectMembers(obj) }