我想在Scala中使用with
关键字获得一些直觉。
一位同事编写了一个完全实现自包含函数的特征,然后使用with
将它混合到几个类中。这种做法对我来说感觉不对,开始了对话。这是他使用trait
with
:
trait Download { def download(url: String): String = {...} }
class DataSet(...) extends Super(...) with Download {
def method(...) { ... download(url) ... }
}
我从Java和C ++来到Scala,并且总是认为with
给我回复了我从C ++中遗漏的多重继承,因此只使用了“is-a”的方式(比如extends
)。他认为语言设计者可以使用更明显的保留词,如isa
,或重复使用extends
而不是with
,如果这是他们的意图。他认为with
旨在让他脱离正常的继承指南,并说他在一些流行的图书馆中看到过这种方式。
我对此持开放态度:with
打算如何使用?如果常用用法不是预期的,with
最常用的是什么?这真的只是一个风格问题吗?
上面示例中针对with
的最强烈论据是with
允许您持有对val d: Download = new DataSet(...)
等实例的引用,该实例提供了d.download(...)
方法与DataSet
根本没有任何关系。那就是:DataSet“is-a”下载并非如此。我主要对使用with
。
答案 0 :(得分:3)
我认为有两种解决方法:
考虑它的继承性,技术性。因此你的论点成立,你应该谨慎地使用它。我大部分时间都遵循这种方法。
考虑一种将东西组合成类或对象的方法。就像你我不喜欢这个,但它在许多图书馆确实发生了很多。一个例子是ScalaTest,它有许多特征,如BeforeAndAfterAll。甚至Martin Odersky(和/或他的团队)也在他们的Coursera课程中使用它。所以这是一个受制裁的做法。这是个好主意吗?我不知道,我猜时间会告诉我。至少它没有Java中的继承的缺点,你只能通过继承使用一件事,并且必须对其余部分做其他事情。这可能会限制一些使我们大多数人在许多用例中避免继承的问题。
答案 1 :(得分:1)
更新 以回应问题更改
你的第一个例子确实是继承。 extends
表示继承,就像在C ++中一样
with
也表示继承,但还有另一种方法可以查看它。声明可以解析为:
class DataSet(...) extends [ Super(...) with Download ]
您将Super with Download
视为包含Super
和Download
的线性化成员的单一类型, 是您正在扩展的内容。以这种方式思考是有意义的:
val x: Super with Download = new DataSet(...)
这是一种非常紧密的耦合,其中继承的download()
方法现在作为DataSet
的方法公开。
肯定 代表is-a
关系;如果您需要Download
的实例,那么您可以成功提供DataSet
的实例来完成工作。
所以DataSet
是一个Download
。尽管这种用法通常被许多基于类的面向对象语言的纯粹主义者所厌恶。主要的反对意见是你应该总是使用最弱的耦合形式(部分原因是鼓励它在C ++编译时产生的改进)
如果你想继承很多方法,这是最简单的方法。如果一个类有很多自动生成的代码与一些自定义逻辑混合在一起,那么它会非常方便。你只需自动生成一个特征并从中继承。它也被用于例如ScalaTest快速导入一堆实用方法&类型。
但是,在这个用例中我不喜欢它。
为什么呢?因为名称如Download
,这个东西显然是打算使用网络连接,这在单元测试时是一个特别糟糕的想法(tm);并且你无法用模拟版本替代替代行为。
您最好的选择是通过Download
构造函数提供DataSet
的实例。它仍然可以是单例,您可以使用默认参数来避免样板:
trait Download {
def apply(url: String): String
}
object DefaultDownload extends Download {
def apply(url: String): String = ...
}
class DataSet(..., download: Download = DefaultDownload) extends Super(...) {
def method(...) { ... download(url) ... }
}