在编程语言课上,我的教授引用mixins作为脆弱基类问题的解决方案之一。维基百科也习惯列出(Ruby)mixins作为脆弱基类问题的解决方案,但不久前someone removed对mixins的引用。我仍然怀疑,对于脆弱的基类问题,它们可能在某种程度上优于继承。否则,为什么教授会说他们有帮助?
我将举例说明可能存在的问题。这是一个简单的Scala实现(Java)问题,教授给我们解释了这个问题。
考虑以下基类。假设这是列表的一些非常有效的特殊实现,并且在其上定义了更多操作。
class MyList[T] {
private var list : List[T] = List.empty
def add(el:T) = {
list = el::list
}
def addAll(toAdd:List[T]) : Unit = {
if (!toAdd.isEmpty) {
add(toAdd.head)
addAll(toAdd.tail)
}
}
}
还要考虑以下特征,它应该将size
添加到上面的列表中。
trait CountingSet[T] extends MyList[T] {
private var count : Int = 0;
override def add(el:T) = {
count = count + 1
super.add(el)
}
override def addAll(toAdd:List[T]) = {
count = count + toAdd.size;
super.addAll(toAdd);
}
def size : Int = { count }
}
问题在于特征是否有效取决于我们如何在基类中实现addAll
,即基类提供的功能是“脆弱的”,就像一个Java中的常规extends
或任何其他编程语言。
例如,如果我们使用上面定义的MyList
和CountingSet
运行以下代码,我们会返回5
,而我们希望获得2
。< / p>
object Main {
def main(args:Array[String]) : Unit = {
val myCountingSet = new MyList[Int] with CountingSet[Int]
myCountingSet.addAll(List(1,2))
println(myCountingSet.size) // Prints 5
}
}
如果我们如下更改基类(!)中的addAll
,则特征CountingSet
将按预期工作。
class MyList[T] {
private var list : List[T] = List.empty
def add(el:T) = {
list = el::list
}
def addAll(toAdd:List[T]) : Unit = {
var t = toAdd;
while(!t.isEmpty) {
list = t.head::list
t = t.tail
}
}
}
请记住,我不是Scala专家!
答案 0 :(得分:6)
如果特质确实让你摆脱困境,那么你已经预料到可能存在问题;然后,您可以参数化您的特性以做适当的事情,或者混合您需要的特性做适当的事情。例如,在Scala集合库中,特征IndexedSeqOptimized
不仅用于指示,而且还以一种表现良好的方式实现各种操作,当索引与任何其他方式一样快时访问集合的元素。 ArrayBuffer
,它包装了一个数组,因此具有非常快速的索引访问(实际上,索引是进入数组的唯一方法!)继承自IndexedSeqOptimized
。相比之下,Vector
可以合理地快速编制索引,但是在没有显式索引的情况下进行遍历会更快,因此它不会。如果IndexedSeqOptimzed
不是特征,那你就不走运了,因为ArrayBuffer
在可变层次结构中,而Vector
在不可变层次结构中,所以无法做出一个共同的摘要超类(至少没有完全搞乱其他继承的功能)。
因此,你脆弱的基类问题没有解决;如果你改变,比如Traversable
的算法实现,使其具有O(n)
性能而不是O(1)
(也许是为了节省空间),你显然不知道是否有些孩子可能会反复使用它并产生O(n^2)
性能,这可能是灾难性的。但是如果你做知道,它会使修复变得更容易:只需混合具有O(1)
实现的正确特征(并且孩子可以在必要时自由地执行此操作)。它有助于将关注点分离到概念上连贯的单位。
因此,总而言之,你可以制造任何脆弱的东西。特质是一种工具,明智地使用它可以帮助你保持健壮,但它们不会保护你免受任何和所有愚蠢。