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个,导致相当多的重复代码。

ScalaDI轻量Scala依赖注入框架

Scala使用Reader Monad实现依赖注入

依赖注入