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

Sem*_*oor 5 extends scala traits mixins

在关于编程语言的课程中,我的教授引用mixins作为脆弱基类问题的解决方案之一.维基百科也习惯列出(Ruby)mixins作为脆弱基类问题的解决方案,但前段时间有人删除了对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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

还要考虑以下特征,它应该添加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 }
}
Run Code Online (Sandbox Code Playgroud)

问题在于特征是否有效取决于我们addAll在基类中的实现方式,即基类提供的功能是"脆弱的",就像extendsJava中的常规或任何其他编程语言一样. .

例如,如果我们运行下面的代码MyList,并CountingSet如上述定义,我们回来5,而我们期望得到2.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我们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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请记住,我不是斯卡拉专家!

Rex*_*err 7

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

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

因此,你脆弱的基类问题没有解决; 如果你改变Traversable一个算法的实现,使其具有O(n)性能而不是O(1)(可能是为了节省空间),你显然无法知道某个孩子是否可能反复使用它并产生O(n^2)可能是灾难性的性能.但是,如果你知道,它使修复容易得多:只是混在一个具有正确的特征O(1)实现(和孩子可以自由地做,只要有必要).它有助于将关注点分解为概念上连贯的单位.

所以,总而言之,你可以做任何脆弱的事情.特质是一种工具,明智地使用它可以帮助你健壮,但它们不会保护你免受任何和所有的愚蠢.