如果你是一个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层。
我们可能有多个UserRepository的实现, 比如JPA, JDBC, Hibernate, iBATIS 等,然后将具体的实现通过Spring注入到 UserService中。这里我们用一个Mock来简单这些这个Trait, 然后模拟Spring注入,(Spring会根据配置和Reflect自动实现实例化和注入,这里我们只是模拟其原理,并没有使用Spring框架):
Scala/Cake pattern实现依赖注入
和上面的代码类似,我们有三个class/trait: UserRepository
, MockUserRepository
和 UserService
, 其中 MockUserRepository
是 UserRepository
的具体实现,
现在我们想把 MockUserRepository
注入到 UserService
。注意 UserService
和 UserRepository
目前没有任何依赖关系。
Scala的蛋糕模式中我们需要声明几个 Component
:
和
这里使用 self-type annotation 声明 UserServiceComponent
需要 UserRepositoryComponent
( this: UserRepositoryComponent =>
)。 如果需要多个依赖,可以使用下面的格式:
剩下的就是注入了,生成一个 ComponentRegistry
对象:
挺漂亮的实现, 如果相应的依赖没有提供,或者拼写错误等,编译时能立刻提示我们。
这还有一个好处就是所有的对象都是 val
类型的。
这样你就可以通过 ComponentRegistry.userService
来使用顶层的组件了。
这样我们可以实现一种"干净"的方式实现不同的组件注入,比如我们想单元测试 userService
,其中它的依赖 userRepository
通过mock的方式提供:
可以很方便的为测试实现新的依赖注入,其中 userRepository
由mock实现。
这就是蛋糕模式的实现。通过 component trait 抽象接口和依赖,最后在一个 ComponentRegistry
注入各个具体的实现。