I knew that sentence would require a much longer and deeper discussion (and I was hoping not to leave OOP to explain it). But in a nutshell, to me interfaces are meant precisely to represent "unknown behavior" and classes for "known behavior".

If we take a second to temporarily forget classes altogether and look at functional programming, you may have something like map(). map() takes a list (well known construct) and an iterator function (unknown outside behavior). The API itself makes it clear what can be changed and what can't. The scope of what is dynamic about the function and what isn't is immediately obvious from the types themselves, completely transparent.

To me, interfaces serve this same role in non-functional programming: they represent outside and dynamic behavior. Classes on the other hand represent known behavior. The problem with "good default implementations" is that they mix these two (opposite) concepts together, which regularly leads to completely "consistent" but absurd outcomes, that are usually attempted to be fixed with language-bandaids. Let me provide two examples.

The first is NSArray and NSMutableArray in Cocoa/Cocoa Touch (apologies for the use of these, it is my strongest background, and ironically enough, one of the closest "correct" subclassing examples in my opinion). NSMutableArray is a subclass of NSArray. This makes no sense. Granted, it makes complete sense from an "implementation" view, but when it comes to user code it makes the classes meaningless: if I make a method that takes an NSArray, the whole point is I'm saying its immutable. But I can pass in a mutable array and none of my expectations will be valid: the array could change right under me. And yet the compiler will be perfectly happy because it is a correct statement, NSMutableArrays are NSArrays. This should theoretically be an edge case: using a subclass to provide the OPPOSITE behavior of the original superclass, rendering the type-system absurd, and yet it is a widely used construct. I am thus forced to defensively copy the immutable array because it may very well be a mutable -- something completely "consistent" in this world. This to me shows this fundamental confusion of interface vs. classes. To get back to what you were saying, the problem with subclassing is precisely that it is incredibly difficult to state what is done "legally".

If you have a pure interface, with no existing "default" behavior, its just like a lambda: anything goes. If you have a class, its static. If you have both (subclassing), it gets incredibly tricky and hard to predict the interaction. The entire API of the class becomes surface area for mutation.

Now let me give you the second example: UIView. UIView has a -subviews method. Why can't I override -subviews to return a static list of views if I never want it to change (thus rendering addSubview: and removeSubview null)? The documentation does NOT list this method as non-overridable (And many people in fact override it to do perfectly legitimate things btw). And if I do override it, I still abide by all the postconditions of the method as defined by the API. And in fact, OTHER parts of the framework totally allow (And sometimes encourage!) this type of "override instead of setting"-style programming. As a novice using the framework, its a completely logical expectation that this should work. So why does UIKit completely break down?

Because the real postconditions are incredibly more complicated than just "Return your subviews". You have part of the code written as if nothing will ever change (internally UIView relies on the _subviews ivar), but another that acts like an interface advertising that it might change (possible UIView subclasses). And half the time, the changes don't upset any delicate balance. But other times, it does. Things like a final keyword wouldn't fix the above problem because again, sometimes its legitimate to override. You can absolutely start writing your code in such a way where things "should" work no matter what method a subclass overrides, but that is very hard to get right (and very hard to test), whereas you get this for free by just forcing everyone to use the external interface! (This is what is meant by breaking encapsulation: you work so hard to provide a sane API, that then anyone can break by changing in otherwise completely legitimate ways). The real problem is that you are saying two contradictory things. What you actually want is to do ONE of the following:

1. Force everyone to use addSubview: and removeSubview: since its a concrete class in a hypothetical non-subclassable Objective-C world. No absurd results because, again, everyone is using the API you worked so hard on and properly tested (i.e. there is no way to futz around with -subviews) OR

2. Have -subviews be a method of some sort of Drawable interface, such that every other piece of code is now known to work with the expectation that it calls that instead of some internal ivar or something, allowing all the shenanigans you want.

Hopefully that makes my views a little clearer, but I'm super tired since its 2AM here so perhaps some of this was more verbose or not as clear as it could be.