Do blends solve the problems of a fragile base class?

In a class in programming languages, my professor cites mixins as one of the solutions to the fragile base class problem. Wikipedia also used mixins to list (Ruby) as a solution to a fragile base class problem, but some time ago someone removed the mixins link. I still suspect that they may somehow have an advantage over inheritance in relation to the fragile base class problem. Otherwise, why did the professor say they help?

I will give an example of a possible problem. This is a simple Scala implementation of the problem (Java) that the professor gave us to illustrate the problem.

Consider the following base class. Assume that this is a very effective custom implementation of the list and that more operations are defined on it.

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) } } } 

Also consider the following attribute, which should add size to the list above.

 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 } } 

The problem is whether this feature will work, depends on how we implement addAll in the base class, that is, the functionality provided by the base class is "fragile", as would be the case with regular extends in Java or any another programming language.

For example, if we run the following code with MyList and CountingSet as defined above, we will go back 5 , whereas we expect to get 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 } } 

If we change addAll in the base class (!) As follows, the CountingSet flag works as expected.

 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 } } } 

Keep in mind that I am not an expert at Scala!

+4
source share
1 answer

Mixins (both traits and something else) cannot completely prevent the fragile base class syndrome and cannot strictly use interfaces. The reason should be pretty clear: everything you can assume about your base class developments, you can also assume about the interface. It helps only because it stops and makes you think, and imposes penalties if your interface gets too large, both of which limit you to necessary and valid operations.

In those cases where the characteristics really save you from trouble, you already expect that a problem may arise; you can then parameterize your trait to do the corresponding thing, or mix in the trait that you need, which does the corresponding thing. For example, in the Scala collection library, the IndexedSeqOptimized attribute IndexedSeqOptimized used to not only indicate but also perform various operations in a way that works well when indexing is as fast as any other way to access the elements of the collection. ArrayBuffer that wraps the array and therefore has very fast index access (indeed, indexing is the only way to the array!) Inherits from IndexedSeqOptimized . On the contrary, Vector can be indexed quickly enough, but it is faster to crawl without explicit indexing, so this is not so. If IndexedSeqOptimzed not a sign, you are out of luck, because ArrayBuffer is in a mutable hierarchy, and Vector is in a mutable hierarchy, so it cannot make a general abstract superclass (at least not without creating a complete mess of other inherited functions).

Thus, your fragile base class problems are not resolved; if you change, say, the Traversable implementation of the algorithm so that it has O(n) performance instead of O(1) (possibly to save space), you obviously cannot know if any child can reuse this and generate O(n^2) performance, which can be disastrous. But if you know, this makes the correction much easier: just mix the right side with the O(1) implementation (and the child can do this every time it becomes necessary). And it helps problem divorces into conceptually coherent units.

So, in general, you can do something fragile. Traits - a tool that is wisely used, can help you be reliable, but they will not protect you from any folly.

+5
source

All Articles