如何创建Scala中的一个正常功能配置的对象? 我曾经看过托尼·莫里斯在视频Reader
单子,我仍然无法连接点。
我有一个硬编码的列表Client
对象:
class Client(name : String, age : Int){ /* etc */}
object Client{
//Horrible!
val clients = List(Client("Bob", 20), Client("Cindy", 30))
}
我想Client.clients
要在运行时确定,与无论是从性能看完文件的灵活性,或从数据库。 在Java世界我会定义一个接口,实现两种电源,并用DI指定一个类变量:
trait ConfigSource {
def clients : List[Client]
}
object ConfigFileSource extends ConfigSource {
override def clients = buildClientsFromProperties(Properties("clients.properties"))
//...etc, read properties files
}
object DatabaseSource extends ConfigSource { /* etc */ }
object Client {
@Resource("configuration_source")
private var config : ConfigSource = _ //Inject it at runtime
val clients = config.clients
}
这似乎是一个非常干净的解决方案,我(不是很多的代码,明确意图),但var
不跳出(OTOH,它似乎并没有对我真的很麻烦,因为我知道这将是注射一次-和-只有一次)。
将在什么Reader
单子看起来像在这种情况下,并解释给我,就像我在5,什么是它的优势是什么?
让我们先从你的方法和之间的简单,肤浅的差异Reader
的方法,这是因为你不再需要挂到config
在任何地方都没有。 比方说,你定义下面隐约聪明型同义词:
type Configured[A] = ConfigSource => A
现在,如果我需要一个ConfigSource
一些功能,说得到第N”客户端列表中的一个功能,我可以声明功能为‘配置’:
def nthClient(n: Int): Configured[Client] = {
config => config.clients(n)
}
因此,我们基本上是拉着一个config
凭空,任何时候我们就需要一个! 闻起来像依赖注入,对不对? 现在,让我们说,我们要在第一,第二和第三的客户年龄在列表中(假设他们存在):
def ages: Configured[(Int, Int, Int)] =
for {
a0 <- nthClient(0)
a1 <- nthClient(1)
a2 <- nthClient(2)
} yield (a0.age, a1.age, a2.age)
对于这一点,当然,你需要一些适当的定义map
和flatMap
。 我不会进入这里,但会简单地说,Scalaz(或Rúnar的真棒NEScala谈话 ,或托尼的 ,你已经看到)给你所有你需要的。
这里最重要的一点是, ConfigSource
依赖和其所谓的注射大多是隐藏的。 唯一的“暗示”,我们可以在这里看到的是, ages
是一个类型的Configured[(Int, Int, Int)]
而不是简单的(Int, Int, Int)
我们并不需要显式引用config
的任何地方。
顺便说一句 ,这是我几乎总是喜欢去想单子的方式:他们隐藏自己的作用 ,所以它不会污染你的代码的流动,同时明确声明的类型签名的效果 。 换句话说,你不必重复自己太多:你说的函数的返回类型“哎,这个功能与作用X交易”,也不要惹它的任何进一步。
在这个例子中,当然效果是从一些固定环境阅读。 你可能熟悉另一单子效果包括错误处理:我们可以说, Option
隐藏的错误处理逻辑,同时使你的方法的类型明确错误的可能性。 或者,那种阅读相反的,在Writer
单子隐藏我们谨此而它的存在在类型系统明确的东西。
现在终于,就像我们通常需要启动一个DI框架(外我们通常控制流的地方,如在一个XML文件),我们还需要引导这个好奇的单子。 当然,我们还会有一些合理的切入点,以我们的代码,如:
def run: Configured[Unit] = // ...
它最终被相当简单:因为Configured[A]
仅仅是功能的类型同义词ConfigSource => A
,我们就可以应用功能,它的“环境”:
run(ConfigFileSource)
// or
run(DatabaseSource)
当当! 因此,与传统的Java风格DI方法对比,我们没有任何“神奇”发生在这里。 唯一的法宝,因为它是被封装在我们的定义Configured
类型,它表现为一个单子的方式。 最重要的是, 该类型的系统让我们诚实面对这“境界”依赖注入在发生:与任何类型Configured[...]
在DI世界,没有任何东西是没有的。 我们根本就没有在老学校DI,这里的一切都可能通过魔法管理得到这个,所以你真的不知道你的代码的某些部分是安全的DI框架之外再利用(例如,您的单位内测试中,或在其他一些项目完全)。
更新:我写了一个博客张贴这也解释了Reader
更详细。