Scala編程的蛋糕模式和依賴注入

jopen 9年前發布 | 74K 次閱讀 Scala Scala開發

 

如果你是一個Java開發者,熟悉 依賴注入 模式, 深度依賴Spring框架的話,在使用Scala做開發時,會遇到一個問題,在Scala世界里,如何實現類似Spring框架的依賴注入呢?

盡管函數式編程的信徒認為他們不需要DI框架,高階(high-order)函數足夠了。但是對于同時支持面向對象的編程和函數式編程的Scala來說,依賴注入是很好的實現應用的一種設計模式。

蛋糕模式(Cake pattern)是Scala實現依賴注入的方式之一。

蛋糕模式是 Scala 之父 Martin Odersky 在其論文 Scalable Component Abstractions 中首先提到。

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

  1. 蛋糕有很多風味, 你可以根據你的需要增加相應的風味。依賴注入就是增加調料。
  2. 蛋糕有很多層 (layer)。如果你想要一個更大的蛋糕你就可以增加更多的層。

我們先以Spring常用的User數據庫讀取的實現為例,看看Scala 風格的依賴注入(蛋糕模式)是如何實現的。

Java/Spring風格的依賴注入實現

一個傳統的Spring實現是將程序劃分為"Repository"層(DAO layer) 和Service層。

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)

}

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框架):

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:

trait UserRepositoryComponent {

val userRepository:UserRepository

}

trait UserServiceComponent {

this : UserRepositoryComponent =>

val userService: UserService

}

這里使用 self-type annotation 聲明UserServiceComponent需要UserRepositoryComponent(this: UserRepositoryComponent =>)。 如果需要多個依賴,可以使用下面的格式:

this : Foo with Bar with Baz =>

剩下的就是注入了,生成一個ComponentRegistry對象:

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的方式提供:

import org.scalatest.mock.MockitoSugar

import 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注入各個具體的實現。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!