尽管我花了几年的时间设计基于依赖注入概念的框架和规范,但我从未特别认为自己是一个狂热的支持者。依赖注入给我的感觉更像是一种特定编程社区的时尚,而不是一种将来框架设计者将在未来语言中复制的持久模式。
确实,流行的模式有时会指向语言级别的一种某种不灵活性。所以我经常问自己,究竟是什么缺失的语言级别特性使得这种特定的模式如此吸引人。我真正找到的唯一答案是,依赖注入是为了弥补这样一个事实:与方法调用不同,实例化在大多数(所有?)面向对象语言中不是一种多态操作。实例化操作会硬编码实例化的具体类,这需要在应该从具体类型中抽象出来的代码中使用某种类型的工厂方法。依赖注入框架的存在是为了驯服工厂方法的泛滥。
在Ceylon中,成员类细化使得实例化在语言级别成为多态操作,但它只适用于成员类型。我认为这是一个非常有用且有趣的特性,但它很难看到它能取代依赖注入。当然,在理论上,你可以将所有你的类定义为某个容器类的成员,但我真的看不到这种习语会流行起来!
因此,我有一个关于语言未来版本的设想,即允许细化顶级声明。所有这一切都将与成员类细化遵循相同的路线。你可以想象一个声明了一个默认或正式顶级类的包,例如
//in package my.stream shared formal class Buffer(Stream stream) { shared formal Byte read(); }
(我们当然也会允许正式和默认顶级方法和属性。)
再次注意Ceylon中正式和抽象的区别:一个正式类可以被实例化。一个抽象类不能被实例化。正式顶级成员使包含的包成为 抽象包
。一个抽象顶级类则不会这样做。到现在你应该明白为什么我们在这里真正需要两种不同的注解。
无论如何,这里的要点是,我们的包中的其他代码将能够创建和使用一个缓冲区,而不依赖于任何具体的子类。
//also in package my.stream shared void open(Stream stream) { Buffer buffer = Buffer(stream); ... Byte b = buffer.read(); ... }
现在,在我们实际使用这个包之前,我们必须 填写
成员的定义。我想象这可能是一种方法:使用正式导入语句但我认为更常见的场景是通过 包扩展 来重用包。我不是很清楚这个语法会是什么样子,可能是在模块描述符中声明一个包扩展另一个包。然后一个
//in package your.backend import my.stream { actual class Buffer(Stream stream) extends super.Buffer(stream) { shared actual Byte read() { ... } } Stream, open }
实际顶级成员将填写的定义缓冲区:
//in package my.stream.fileio actual class Buffer(Stream stream) extends super.Buffer(stream) { shared actual Byte read() { ... } }
我不太确定,但我预计还需要一个额外的功能:在模块描述符中重新连接包间引用的能力。例如,在your.backend包中,你可能会导入 my.stream,但模块描述符会声明对抽象包my.stream的引用实际上由my.stream.fileio.
包来满足
好吧,所有这些都只是现在的推测,我甚至还没有真正考虑过如何将这些映射到JVM上。但我认为你可以看到这可以成为依赖注入的替代方案。我特别喜欢的一点是,它使Ceylon语言更加规范,同时增加了有用的功能。