当被问及 Scala 中的依赖注入时,很多答案都指向使用 Reader Monad,要么是来自 Scalaz 的,要么就是自己开发的。有很多非常清晰的文章描述了这种方法的基础(例如 鲁纳的话,杰森的博客) ,但是我没有找到一个更完整的例子,我也没有看到这种方法相对于传统的“手动”DI 的优势(参见 我写的指南)。很可能我忽略了一些重要的问题,所以才有这个问题。
举个例子,假设我们有这些类:
trait Datastore { def runQuery(query: String): List[String] }
trait EmailServer { def sendEmail(to: String, content: String): Unit }
class FindUsers(datastore: Datastore) {
def inactive(): Unit = ()
}
class UserReminder(findUser: FindUsers, emailServer: EmailServer) {
def emailInactive(): Unit = ()
}
class CustomerRelations(userReminder: UserReminder) {
def retainUsers(): Unit = {}
}
在这里,我使用类和构造函数参数来建模,这与“传统的”DI 方法非常匹配,但是这种设计有两个好的方面:
UserReminder
不知道 FindUsers
需要数据存储。这些功能甚至可以在单独的编译单元中IO
单子中的值等等。这怎么能与阅读器单体模型?最好保留上面的特征,这样就可以清楚每个功能需要什么样的依赖关系,并将一个功能的依赖关系隐藏起来。请注意,使用 class
es 更多的是一个实现细节; 也许使用 Reader monad 的“正确”解决方案将使用其他内容。
我确实发现了一个 有点相关的问题表明:
然而,除了对于这样一个简单的事情来说有点太复杂(但这是主观的)之外,在所有这些解决方案中,例如 retainUsers
方法(调用 emailInactive
,调用 inactive
来查找非活动用户)需要了解 Datastore
的依赖关系,以便能够正确地调用嵌套的函数——或者我错了?
在哪些方面,对于这样的“业务应用程序”,使用 Reader Monad 会比仅仅使用构造函数参数更好?