After reading all the comments of many confused and curious, here is when inheritance is bad and why so _in the absence of any performance considerations_. 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…
> 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. I'm not sure what's so special about overwriting methods here. There are many cases where semantics can break as a dependency changes. One example would be whether a callback is executed on the same thread (or event-loop tick) or in the backgroun…
If we cannot assume synchronous execution, then semantics can break exactly as you said. That a rather orthogonal problem from the inheritance one and my suggested solution is to make use of pure functional programming (hence purity can be assumed unless the method signature indicates that an effect might happen). Here is an example of how a potentially async Stack might look like:
class Stack(...) extends Stack {
def push(element) returns IO[nothing] = ...
def pop() returns IO[element] = ...
}
The IO would describe an action that can be ran at a later point, which means when the method has being called and returned, nothing has happened until you call `run(io)`.That way you will be immediately aware of the behavior and adapt your Countstack as follows:
class CountingStack(underlyingStack) extends Stack {
override def push(element) returns IO[nothing] =
underlyingStack.push(element).andThen(nothing => count += 1; return nothing)
override def pop() returns element =
underlyingStack.pop(element).andThen(element => count += 1; return element)
}
That ensures that order of events is being kept and also that failures (e.g. a concurrent thread dies in the middle) are handled.> Assuming that the Stack class specifies that all modifications will go through the push and pop methods
This is a severe restriction on how the Stack can be implemented then and it bears the risk of someone violating this specification by accident (think about the famous equals/hashCode specification in Java).