
The Impoliteness of Overriding Methods (2012) - tosh
http://journal.stuffwithstuff.com/2012/12/19/the-impoliteness-of-overriding-methods/
======
kazinator
Covered in Common Lisp by custom method combinations.

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_.

------
icebraining
It's interesting, but I can see it leading to much frustration when, as an
overrider, you _don 't_ want it to run that before/after code.

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

~~~
barrkel
Another perspective: the developer doing the overriding doesn't in fact have
more information; they don't know the future plans of the developer of the
class they're overriding, and they don't know about the usage models of other
descendants of the class and how they'll affect evolution into the future. The
more "power" they use, the more likely they'll be left behind on a version
bump.

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.

~~~
icebraining
The library developers can provide that information without enforcing it. For
example, in Python any method starting with an underscore is considered
private and liable to be changed or removed, but the language doesn't treat
them in a special way.

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.

------
derefr
Or: OOP theory essentially says that classes should never be "subclassed." As
in, the code isn't supposed to evolve that way; you're Doing It Wrong.

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

~~~
rhapsodic
_> Or: OOP theory essentially says that classes should never be "subclassed."
As in, the code isn't supposed to evolve that way; you're Doing It Wrong._

Got a reference for that? I've heard "prefer composition over inheritance"
(GoF) but never have I heard that classes should never be subclassed.

~~~
avinium
The cleanliness and "refactorability" of my code improved significantly when I
drastically reduced the amount of subclassing I was doing.

I wouldn't go so far to say "never subclass", but I'd say _most_ of the time,
you won't need to subclass.

~~~
rhapsodic
_> The cleanliness and "refactorability" of my code improved significantly
when I drastically reduced the amount of subclassing I was doing.

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

~~~
avinium
Learning Clojure was, IMO, the best thing I could have done to improve my
development skills.

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.

------
jasode
Fyi... a related debate is _" sealed classes"_ by default.

E.g. Eric Lippert's comments:
[https://blogs.msdn.microsoft.com/ericlippert/2004/01/22/why-...](https://blogs.msdn.microsoft.com/ericlippert/2004/01/22/why-
are-so-many-of-the-framework-classes-sealed/)

A StackOverflow q&a: [https://stackoverflow.com/questions/268251/why-seal-a-
class/...](https://stackoverflow.com/questions/268251/why-seal-a-class/268287)

------
victor___
It seems like the debate boils down to "should subclasses be able to override
behaviour in ways the base class doesn't anticipate?".

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.

~~~
andybak
> 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"

------
fiddlerwoaroof
Common Lisp’s object system (CLOS) allows you to specify on a method by method
basis whether or not the most-specific method has priority or the least-
specific one does. Generally people just use the default behavior—least
specific first—but this post makes the alternative sound interesting.

~~~
ScottBurson
It also has daemon methods, which solve the problem of making sure the
superclass gets to do something before, or after, the subclass method runs,
without the subclass method having to request this explicitly.

~~~
mst
Attempting to google variants on the them of "CLOS daemon methods" isn't
bringing up anything recognisable as what you're describing - happen to know
how to find a link?

(I love it when my attempt to RTFM is defeated by my being too stupid to find
the FM in the first place ;)

~~~
TeMPOraL
The FM for Common Lisp is called Hyperspec, or CLHS. Just append "CLHS" to
your search query.

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".

~~~
ScottBurson
I guess the term isn't as common as I thought, but it appears to have been
used explicitly in the Flavor system, which was the object system used on the
MIT Lisp Machines and which predated CLOS. That must be where I picked it up.

~~~
lispm
A daemon / demon was a procedure which runs when a slot is accessed / added /
deleted / .... My bet is that this name was used in frame languages in the end
70s where there were if-needed, if-added, ... demons for a frame slot. Flavors
probably got it from there - though I'm not sure.

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.

------
FRex
I've read about this (public non virtual interfaces calling virtual functions
that are meant to be overridden) for C++ a long time ago:
[http://www.gotw.ca/publications/mill18.htm](http://www.gotw.ca/publications/mill18.htm)

~~~
Sharlin
Well, it's the Template Method pattern from the GoF book and, like most design
patterns, probably independently discovered by most people writing OO code.

------
cjhanks
Yes, I think in C++ at-least you are supposed to protect from this by never
having the child directly implement the virtual method.

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

~~~
tomjakubowski
C++11 added final for both virtual member functions and for classes. No need
for C++2x metaclasses.

~~~
cjhanks
Sorry, what are the C++2x metaclasses? I haven't been following along very
closely.

------
_bxg1
This is an awesome pattern. Seems like it could easily be added as a language
feature to Java and C#. Add an alternative to the "virtual" keyword called
"extensible", and an alternative to "override" called "extend".

~~~
masklinn
Isn't it pretty trivial to implement?

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.

~~~
emsy
That’s basically the template method pattern and I remember some Windows API
using it (maybe GDI) where the protected overridable method was called
DrawInternal and the public Draw method was non-virtual.

------
_bxg1
I always thought V8 got its name from being a "fast and powerful engine"

~~~
gaius
It’s also a health drink
[https://en.m.wikipedia.org/wiki/V8_(beverage)](https://en.m.wikipedia.org/wiki/V8_\(beverage\))

------
tzahola
Whenever I’ve worked, I was always advocating to use subclassing as a last
resort solution. It’s the most intimate and brittle relationship you can have
between your classes.

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

~~~
scarface74
I've hardly ever found a good reason to inherit functionality - plain old data
objects yes, but I always favor aggregation over inheritance.

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.

------
username_my
Wow ... I usually lurk hackernews and don’t comment but I feel offended by
this post.

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)

