0
0

Scala编程的蛋糕模式和依赖注入

鸟窝 发表于 2015年09月03日 14:09 | Hits: 2051
Tag: Scala

如果你是一个Java开发者,熟悉依赖注入模式, 深度依赖Spring框架的话,在使用Scala做开发时,会问一个问题,在Scala世界里,如何实现类似Spring框架的依赖注入呢?

尽管函数式编程的信徒认为他们不需要DI框架,高阶(high-order)函数足够了。但是对于同时支持面向对象的编程和函数式编程的Scala来说,依赖注入是很好的实现应用的一种设计模式。
蛋糕模式(Cake pattern)是Scala实现依赖注入的方式之一。
蛋糕模式是 Scala 之父 Martin Odersky 在其论文Scalable Component Abstractions中首先提到。

什么是蛋糕模式呢? 一个非正式的但是很形象的解释是:

  • 蛋糕有很多风味, 你可以根据你的需要增加相应的风味。依赖注入就是增加调料。
  • 蛋糕有很多层 (layer)。如果你想要一个更大的蛋糕你就可以增加更多的层。

我们先以Spring常用的User数据库读取的实现为例,看看Scala 风格的依赖注入(蛋糕模式)是如何实现的。

Java/Spring风格的依赖注入实现

一个传统的Spring实现是将程序划分为"Repository"层(DAO layer) 和Service层。

1234567891011121314
case class User(name:String)trait UserRepository {  def create(user: User)  def find(name: String)  def update(user: User)  def delete(user: User)}class MockUserRepository extends UserRepository {  def create(user: User) = println("creating user: " + user)  def find(name: String) = println("finding user: " + name)  def update(user: User) = println("udating user: " + user)  def delete(user: User) = println("deleting user: " + user)}
123456789101112
class UserService {  @Resource  var userRepository: UserRepository = _  def create(user: User) = userRepository.create(user)  def find(name: String) = userRepository.find(name)  def update(user: User) = userRepository.update(user)  def delete(user: User) = userRepository.delete(user)}

我们可能有多个UserRepository的实现, 比如JPA, JDBC, Hibernate, iBATIS 等,然后将具体的实现通过Spring注入到 UserService中。
这里我们用一个Mock来简单这些这个Trait, 然后模拟Spring注入,(Spring会根据配置和Reflect自动实现实例化和注入,这里我们只是模拟其原理,并没有使用Spring框架):

1234567
object Main extends App {  val service = new UserService()  val userRepository = new MockUserRepository()  service.userRepository = userRepository // inject userRepository into userService  service.create(User("user")) }

Scala/Cake pattern实现依赖注入

和上面的代码类似,我们有三个class/trait:UserRepository,MockUserRepository和UserService, 其中MockUserRepository是UserRepository的具体实现,
现在我们想把MockUserRepository注入到UserService。注意UserService和UserRepository目前没有任何依赖关系。

Scala的蛋糕模式中我们需要声明几个 Component :

1234
trait UserRepositoryComponent {  val userRepository:UserRepository}
1234
trait UserServiceComponent {  this: UserRepositoryComponent =>  val userService: UserService}

这里使用self-type annotation声明UserServiceComponent需要UserRepositoryComponent(this: UserRepositoryComponent =>)。 如果需要多个依赖,可以使用下面的格式:

1
this: Foo with Bar with Baz =>

剩下的就是注入了,生成一个ComponentRegistry对象:

1234
object ComponentRegistry extends UserServiceComponent with UserRepositoryComponent {  override val userRepository: UserRepository = new MockUserRepository  override val userService: UserService = new UserService(userRepository)}

挺漂亮的实现, 如果相应的依赖没有提供,或者拼写错误等,编译时能立刻提示我们。
这还有一个好处就是所有的对象都是 val 类型的。

这样你就可以通过ComponentRegistry.userService来使用顶层的组件了。

这样我们可以实现一种"干净"的方式实现不同的组件注入,比如我们想单元测试 userService ,其中它的依赖 userRepository 通过mock的方式提供:

1234567891011121314
import org.scalatest.mock.MockitoSugarimport org.mockito.Matchers._import org.mockito.Mockito._trait TestEnvironment extends UserServiceComponent with UserRepositoryComponent {  val userRepository = MockitoSugar.mock[UserRepository]  val userService = new UserService(userRepository)}object Test extends App with TestEnvironment{  val user = User("user")  userService.create(user)  verify(userRepository, times(1)).create(any[User])}

可以很方便的为测试实现新的依赖注入,其中 userRepository 由mock实现。

这就是蛋糕模式的实现。通过 component trait 抽象接口和依赖,最后在一个ComponentRegistry注入各个具体的实现。

参考文档

  1. Scalable Component Abstractions
  2. Real-World Scala: Dependency Injection (DI)
  3. DI in Scala: Cake Pattern pros & cons
  4. Dependency injection vs. Cake pattern
  5. Cake pattern in depth
  6. Cake Pattern in Scala / Self type annotations / Explicitly Typed Self References - explained

原文链接: http://colobu.com/2015/07/28/Scala-Cake-pattern-and-Dependency-Injection/

0     0

我要给这篇文章打分:

可以不填写评论, 而只是打分. 如果发表评论, 你可以给的分值是-5到+5, 否则, 你只能评-1, +1两种分数. 你的评论可能需要审核.

评价列表(0)