mixins能解决脆弱的基类问题吗?

时间:2013-01-23 17:36:11

标签: scala mixins extends traits

在编程语言课上,我的教授引用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或任何其他编程语言。

例如,如果我们使用上面定义的MyListCountingSet运行以下代码,我们会返回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专家!

1 个答案:

答案 0 :(得分:6)

Mixins(无论是特征还是其他)都不能完全防止脆弱的基类综合症,也不能严格使用界面。原因应该是非常明确的:你可以假设关于你的基类的工作,你也可以假设一个接口。它只会因为它停止并让你思考而有所帮助,并且如果你的界面变得太大会造成样板惩罚,这两者都会限制你进行必要和有效的操作。

如果特质确实让你摆脱困境,那么你已经预料到可能存在问题;然后,您可以参数化您的特性以做适当的事情,或者混合您需要的特性做适当的事情。例如,在Scala集合库中,特征IndexedSeqOptimized不仅用于指示,而且还以一种表现良好的方式实现各种操作,当索引与任何其他方式一样快时访问集合的元素。 ArrayBuffer,它包装了一个数组,因此具有非常快速的索引访问(实际上,索引是进入数组的唯一方法!)继承自IndexedSeqOptimized。相比之下,Vector可以合理地快速编制索引,但是在没有显式索引的情况下进行遍历会更快,因此它不会。如果IndexedSeqOptimzed不是特征,那你就不走运了,因为ArrayBuffer在可变层次结构中,而Vector在不可变层次结构中,所以无法做出一个共同的摘要超类(至少没有完全搞乱其他继承的功能)。

因此,你脆弱的基类问题没有解决;如果你改变,比如Traversable的算法实现,使其具有O(n)性能而不是O(1)(也许是为了节省空间),你显然不知道是否有些孩子可能会反复使用它并产生O(n^2)性能,这可能是灾难性的。但是如果你知道,它会使修复变得更容易:只需混合具有O(1)实现的正确特征(并且孩子可以在必要时自由地执行此操作)。它有助于将关注点分离到概念上连贯的单位。

因此,总而言之,你可以制造任何脆弱的东西。特质是一种工具,明智地使用它可以帮助你保持健壮,但它们不会保护你免受任何和所有愚蠢。