5

プログラミング言語のクラスで、私の教授は、壊れやすい基本クラスの問題に対する解決策の 1 つとしてミックスインを引用しています。ウィキペディアも (Ruby) ミックスインを壊れやすい基底クラスの問題の解決策として挙げていましたが、しばらく前に誰かがミックスインへの言及を削除しました。壊れやすい基本クラスの問題に関して、継承よりも何らかの形で利点があるのではないかと私はまだ思っています。そうでなければ、なぜ教授は彼らが助けになると言うでしょうか?

考えられる問題の例を挙げます。これは、教授が問題を説明するために私たちにくれた (Java) 問題の単純な Scala 実装です。

次の基本クラスを検討してください。これは非常に効率的なリストの特別な実装であり、さらに多くの操作が定義されていると仮定します。

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が基本クラスでの実装方法に依存することです。つまり、基本クラスによって提供される機能はextends、Java やその他のプログラミング言語の通常の場合と同様に「脆弱」です。 .

たとえば、上記で定義したようにMyListとを指定して次のコードを実行すると、 が返されますが、 が返されることが期待されます。CountingSet52

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 の専門家ではないことに注意してください。

4

1 に答える 1

7

ミックスイン (トレイトであろうと他のものであろうと) は、脆弱な基底クラス シンドロームを完全に防ぐことはできず、インターフェイスを厳密に使用することもできません。その理由は明らかです。基本クラスの動作について想定できることはすべて、インターフェイスについても想定できます。停止して考えさせられるという理由だけで役立ちます。インターフェイスが大きくなりすぎると、定型的なペナルティが課せられます。どちらも、必要かつ有効な操作に制限される傾向があります。

特性が本当に問題から抜け出すのは、問題があるかもしれないとすでに予想している場所です。次に、適切なことを行うためにトレイトをパラメータ化するか、適切なことを行う必要なトレイトを混在させることができます。たとえば、Scala コレクション ライブラリでは、インデックス作成がコレクションの要素にアクセスする他の方法と同じくらい高速である場合に、さまざまな操作を IndexedSeqOptimized示すだけでなく実装するためにもトレイトが使用されます。ArrayBufferは配列をラップするため、非常に高速なインデックス アクセスが可能です (実際、インデックスは配列への唯一の方法です!) は から継承しIndexedSeqOptimizedます。対照的に、Vectorかなり高速にインデックスを作成できますが、明示的なインデックス作成なしでトラバーサルを実行する方が高速であるため、そうではありません。もしもIndexedSeqOptimzedArrayBuffer可変階層にありVector、不変階層にあるため、共通の抽象スーパークラスを作成できませんでした (少なくとも、他の継承された機能を完全に混乱させることなく)。

したがって、壊れやすい基本クラスの問題は解決されません。たとえば、Traversableのアルゴリズムの実装を(おそらくスペースを節約するために)O(n)ではなくパフォーマンスが向上するように変更した場合、一部の子供がそれを繰り返し使用して破滅的なパフォーマンスをO(1)生成する可能性があるかどうかを明らかに知ることはできません。O(n^2)しかし、知っていれば修正ははるかに簡単になります: 実装を持つ適切なトレイトを混ぜるだけですO(1)(そして、必要に応じて、子はいつでも自由にそれを行うことができます)。また、離婚の懸念を概念的に一貫した単位に分割するのに役立ちます。

つまり、要約すると、何でも壊れやすいものにすることができます。特性は、賢く使用すれば堅牢になるためのツールですが、すべての愚行から保護されるわけではありません。

于 2013-01-23T18:50:55.343 に答える