The Impoliteness of Overriding Methods (2012)
journal.stuffwithstuff.com
journal.stuffwithstuff.com
A "method combination" determines, when a generic function call denotes multiple methods, in which order they are called.
There is a default method combination with standard features: :pre/:post/:around auxiliary methods in relation to a primary method. This has the conventional ordering: derived controls dispatch. E.g. an :around method can decide that the primary method won't be executed at all by not calling (call-next-method).
The documentation for the define-method-combination macro is a bit of a challenging read. One of the examples of its "long forms" shows how the standard method combination would be defined with this macro, if it didn't exist. A simple example shows how to arrange for methods to be dispatched one by one until one returns something other than nil.
The thing is, the developer doing the overriding generally has more information about the final system than the original - after all, the code is more specific. So taking away their power is generally problematic.
I could see it being useful if it was optional. Something like:
class ScaryMonster extends GameObject {
void render(Renderer renderer) {
super(function() {
renderer.drawImage(Images.SCARY_MONSTER);
});
}
}Implementation inheritance means accepting an extremely tight coupling to the ancestor, and overriding is the main mechanism of coupling. Coupling means correlated changes, and when the developers of the ancestor and descendant are in different organizations, it means versioning risk. This is an argument for very careful selection of methods available for overriding, and strictly limiting the choices of the descendant, funneling them into designed-for usage modes.
Versioning is single biggest reason why C# elected to make methods non-virtual by default, where Java makes them virtual by default. Breaking all the clients of your code because of an implementation design change isn't very desireable, thus limiting the surface area for coupling is usually a good idea.
If ancestors and descendants are in the same repository so upgrades of definition site and use site can be coordinated, or the probability of design change in the ancestor is low, then these concerns are more relaxed. But they're valid in many situations.
Enforcement mechanisms don't have anything to do with passing information, but with removing liability (not necessarily legal - might only be social) from the original developers. They're effectively an EULA.
It's about ensuring the invariants the parent class needs -- not giving subclasses freedom to do whatever they want is meant as a feature.
This allows library designers to design more future proof APIs (since they control more invariants of how they're gonna be extended), and to prevent users of their libs from shooting themselves in the foot.
Is it because of a design issue in the Android libraries or to try and circumvent some limitations that might have some reasons to exist?
As updates are rarely ever applied, you have to deal with 10 year old versions frequently.
So you use these tricks to get the same functionality on old versions.
Sometimes you also need to do the same on modern versions, because Google releases UI libraries where everything is final and internal, and so you either have to fork a library with hundredthousands of LOC, or you gotta use reflection.
As on android every library is bundled statically, there’s no risk of anything breaking either.
That's not a programming problem that this capability allows us to solve though.
That's a management/market/etc problem, that necessitates that we use this method as a kludge to bypass it.
In OOP, the only reason for something to be a "subclass" of something else, is that you first had several subclasses (FooA, FooB, FooC) as plain classes, and then noticed that they all obeyed the exact same interface (a hypothetical IFoo) and had common logic, and so you then took the opportunity to factor out the interface + common logic into a common superclass (Foo).
The interface, if separated into an abstract contract (a real IFoo) is reusable by downstream code; but the common logic, 99% of the time, isn't. (It's potentially reusable by people contributing to your library, but more-than-likely they'll have to modify the "shape" of your common logic when they add a new concrete subclass with its own new concerns, even if the contract IFoo would stay the same.)
Given a full prover-level type system, you would be essentially unable to usefully do anything with the superclass Foo once it's been created, other than what the "causally prior" subclasses FooA, FooB, and FooC are already doing with it. Foo is the reificiation of an equivalence class whose members are exactly FooA, FooB, and FooC. A "FooD" shouldn't be able to be defined without changing the definition of Foo.
Assuming you accept this, how does code reuse work?
• Specializing behavior: this is what the delegate pattern (composition!) is for. Rather than SomeLib exposing a base class SomeLib.Foo for subclassing, SomeLib instead gives you a (final, sealed) SomeLib.Foo class which takes a SomeLib.IBar as a delegate, and calls methods on it. You create YourLib.Bar that implements SomeLib.IBar, and pass it to the constructor of a new SomeLib.Foo.
• Extending behavior: this is what plain-old component-wise composition is for. Call it an Adapter, or a Decorator, or a Facade; it's just a new object of a class you've defined, that is implemented by holding onto an instance of the (final, sealed) SomeLib.Foo class. (If you want nice interoperation, the upstream library author should provide a SomeLib.IFoo interface and define their methods to take/return it; and you should have your wrapper class implement SomeLib.IFoo. In less statically-typed languages, this translates to adding a contract method to your object that can be probed for, e.g. Ruby's #to_str.)
Got a reference for that? I've heard "prefer composition over inheritance" (GoF) but never have I heard that classes should never be subclassed.
I wouldn't go so far to say "never subclass", but I'd say most of the time, you won't need to subclass.
>I wouldn't go so far to say "never subclass", but I'd say most of the time, you won't need to subclass.
Yeah, I would agree with that. A lot of OOP mechanisms like inheritance, or design patterns, get overused a lot. I'm not saying it's what you were doing, but I've seen it mostly in code written by newish programmers. It seems that once the concepts of OOP click in their mind, they get excited and eager to put them to work, so they use them where they're not really appropriate. I was definitely guilty of that, way back in the day. But once the novelty wore off, and I realized that I was just creating over-architected messes, I quit doing it.
It helps you realize that programming is simply about data, and how you manipulate it.
Design patterns, OOP, conventions, etc are all just secondary tools that sometimes help you do that.
Well, whatever they said, they started off with "OOP theory essentially says that..."
So, if it's OO theory, I'd appreciate if someone could provide me a reference that shows that it's OO theory, and not just someone's personal opinion.
E.g. Eric Lippert's comments: https://blogs.msdn.microsoft.com/ericlippert/2004/01/22/why-...
A StackOverflow q&a: https://stackoverflow.com/questions/268251/why-seal-a-class/...
Pro: sometimes it lets you extend classes in useful ways that the original author didn't anticipate. Con: it makes it difficult or impossible to reason about invariants in the base class.
Seems like it's a trade-off between hack-ability and robustness.
Precisely. This is where Python's "We're all adults, here" philosophy comes into play: just trust me with the footgun. I'll take responsibility for the risk but dammit, if I'm not going to be pissed when I can't do something I know I need to do.
Maybe it's the distinction between "languages for large teams of mediocre programmers" vs "languages for hackers or small experienced teams"
(I love it when my attempt to RTFM is defeated by my being too stupid to find the FM in the first place ;)
The parent is probably talking about method combinations - in particular, the method combination used by default, which supports :before, :after and :around methods.
It's the first time I see the term "daemon method".
But the concept/term of a demon goes back to the end 60s to Hewitt's planner language and probably even earlier.
Later with generic functions in CLOS, I'd guess the term wasn't used, since something like an :after method is no longer conceptually a specific demon procedure attached to a slot via a facet, but a method in a method combination.
https://franz.com/support/documentation/8.1/doc/flavors.htm#...
> Great systems also have advice. There's no universally accepted name for this feature. Sometimes it's called hooks, or filters, or aspect-oriented programming. As far as I know, Lisp had it first, and it's called advice in Lisp. Advice is a mini-framework that provides before, around, and after hooks by which you can programmatically modify the behavior of some action or function call in the system. Not all advice systems are created equal. The more scope that is given to an advice system — that is, the more reach it has in the system it's advising — the more powerful the parent system will be.
Or else I will wrap their objects with mine such that mine have new, unrelated methods that do not automatically yield control to those underlying object. My methods will call theirs at any point in the processing that I wish.
Basically, I will get the last word no matter what, so either work with me by giving me a normal OOP system, or I will build detours around you anyway.
This means inheritors only need to ensure they implement according to the specified constraints.
I also suspect some of the new C++14 and beyond meta tagging can even enforce a `final`.
class Abstract {
public:
// Even though this function might do nothing but
// redirect, it is a useful abstraction.
void
SomeMethod() {
// Do things which must be done first, if any.
ImplSomeMethod();
// Do things which must be done after, if any.
}
protected:
// Document all expected invariant behavior here.
virtual void
ImplSomeMethod() = 0;
};Define an "inner" interface, set up the "outer" object with a default implementation (available to caller so they can wrap it rather than replace it entirely) and the "inner" calls simply delegate to it. Make all classes involved sealed/final.
The degenerate case is the humble callback.
Unfortunately the majority of OOP books don’t emphasize this, so beginners try to do everything by subclassing, as if that was what OOP is about...
When it comes to inheriting a base object to reuse cross cutting concerns, not only do you run into the fragile base class problem - there is usually a better way.
I personally prefer aspect oriented programming when possible.
If he started with; say “in javascript” I’d agree completely.
And I know that unless you‘re writing code for others to use (SDK, os ..) you usually don’t need inheretance.
But for generations people overridden methods and no one complained about it... infact it’s a good idea in so many cases and easily any proper OOP class can protect it’s behaviour (see encapsulation ..)
The only impolitness I see here is someone refusing a very valid pattern in a very solid space just because it’s invalid in js (where a lot of things are invalid)