First, inheritance from an interface/trait is totally okay. The problem is class inheritance, meaning implementation inheritance.
There are two cases:
1) you inherit from a class and only add methods but don't overwrite anything. This is the good case, you can do that
2) you inherit from a class and overwrite one ore more of its methods. This is the bad case, no matter how you look at it. And here is why.
Look at the following classical simple Stack class:
class Stack(...) {
def push(element) returns nothing = ...
def pushAll(elementlist) returns nothing = ...
def pop() returns element = ...
}
Now imagine you want to keep a count of the number of elements, so you decide to extend the class: class CountingStack(...) extends Stack {
override def push(element) returns nothing =
count += 1; super.push(element)
override def pushAll(elementlist) returns nothing =
count += elementlist.size; super.pushAll(elementlist)
override def pop() returns element =
count -= 1; super.pop(element)
}
Looks good no? Can you spot the problem?If the Stack class implements pushAll as iterating over the elementlist and calling push for each element, then the CountingStack will count twice!
We could try to just not overwrite pushAll in CountingStack - but what if the Stack class is later changed to implement pushAll without calling push? Then CountingStack would now miss counts.
This problem is impossible to fix and it is a general problem - when overwriting a method, you can never be sure that semantics could break when the base class is changed.
So what is the solution here? Simple: use composition over inheritance.
class CountingStack(underlyingStack) {
def push(element) returns nothing =
count += 1; underlyingStack.push(element)
override def pushAll(elementlist) returns nothing =
count += elementlist.size; underlyingStack.pushAll(elementlist)
override def pop() returns element =
count -= 1; underlyingStack.pop(element)
}
Problem solved - no matter how Stack is implemented, our CountableStack will always be correct. The drawback (if you want to call it one) is that we need some common interface/trait that both Stack and CountableStack will implement.I hope that sheds some additional light on why (implementation) inheritance is often considered a bad practice.