Scala依赖注入Cake模式
Scala的依赖注入无需额外框架。
假设有一个User:
sealed case class User(username: String)
我们要创建仓储服务UserRepository ,首先创建接口:
trait UserRepositoryComponent { // For expressing dependencies
def userRepository: UserRepository // Way to obtain the dependency
trait UserRepository { // Interface exposed to the user
def find(username: String): User
}
}
- UserRepositoryComponent trait用来表达依赖,它包含下面组件:
- 得到依赖:def userRepository 为什么使用def不用val? 如果使用的val,所有的实现都被锁定,并必须提供单一的依赖性实例(一个常数)。这不是实现组件依赖的好方式。
- 接口自己, 有一个UserRepository trait, 实现根据名称查询用户功能。
实现接口的代码:
trait UserRepositoryComponentHibernateImpl
extends UserRepositoryComponent {
def userRepository = new UserRepositoryImpl
class UserRepositoryImpl extends UserRepository {
def find(username: String): User = {
println("Find with Hibernate: " + username)
new User(username)
}
}
}
目前为止我们准备好了一个组件UserRepositoryComponent,Scala的依赖是指组件之间的依赖,Scala的self-type annotations 能实现组件之间的依赖。假设UserAuthorization 组件需要依赖上面的UserRepository:
// Component definition, as before
trait UserAuthorizationComponent {
def userAuthorization: UserAuthorization
trait UserAuthorization {
def authorize(user: User)
}
}
// Component implementation
trait UserAuthorizationComponentImpl
extends UserAuthorizationComponent {
// Dependencies依赖
this: UserRepositoryComponent =>
def userAuthorization = new UserAuthorizationImpl
class UserAuthorizationImpl extends UserAuthorization {
def authorize(user: User) {
println("Authorizing " + user.username)
// Obtaining the dependency and calling a method on it
userRepository.find(user.username)
}
}
}
重要的是,UserRepositoryComponent =>.这里规定UserAuthorizationComponentImpl 需要 theUserRepositoryComponent组件的实现. 这将会将UserRepositoryComponent带入这段scope,
Wiring
不同组件如何一起工作?
val env = new UserAuthorizationComponentImpl
with UserRepositoryComponentHibernateImpl
env.userAuthorization.authorize(User("1"))
首先构造一个环境,绑定我们要用的所有组件实现, 下一步我们在环境上调用这个组件,注入就实现了
测试:
val envTesting = new UserAuthorizationComponentImpl
with UserRepositoryComponent {
def userRepository = mock(classOf[UserRepository])
}
envTesting.userAuthorization.authorize(User("3"))
如果组件运行时需要的是一些数据,如 UserInformation 服务需要的是 User这个数据对象?
// Interface
trait UserInformationComponent {
//需要创建的组件
def userInformation(user: User)
trait UserInformation {
def userCountry: Country
}
}
// Implementation
trait UserInformationComponentImpl
extends UserInformationComponent {
// Dependencies依赖
this: CountryRepositoryComponent =>
def userInformation(user: User) = new UserInformationImpl(user)
class UserInformationImpl(val user: User) extends UserInformation {
def userCountry: Country {
// Using the dependency使用依赖
countryRepository.findByEmail(user.email)
}
}
}
// Usage
val env = new UserInformationComponentImpl
with CountryRepositoryComponentImpl
env.userInformation(User("someuser@domain.pl")).userCountry
这样好于将User 作为UserService的方法传入。
问题:
假设有三个组件:
val env = new MysqlDatabaseComponentImpl
with UserRepositoryComponent
with UserAuthenticatorComponent
创建一个测试:
val env = new MockDatabaseComponentImpl
with UserRepositoryComponent
with UserAuthenticatorComponent
如果不是三个组件,而是20个,导致相当多的重复代码。