2

Some of the rants that I’ve read recently have inspired me to write this one:

You know how some OOP based APIs require you to call the base implementation of an overridden method?
If you think about it, its pretty shit. None of the languages have mechanisms to enforce it, so all you can do is to rely on the caller to read the docs for that method that he is overwriting and then do the right thing.
And then you can also have the requirement that the base implementation should be called at the start or at the end of that method.

I really think that this is an OOP problem because if I would have to design it, I’d make a function that takes a closure as a parameter and then call that closure at the start or at the end of that "base" code. This is implicitly documented (by naming the closure appropriately so that the caller knows if it is called at start or end). And it is impossible to miss it because you need to pass something to that parameter. (Alternatively, you could also pass the closure to the constructor).

Comments
  • 1
    It's not an OOP Problem, It's a design problem. Because that's shit design. The solution is to use abstract methods, callbacks and/or default interfaces to do this properly.

    Private call(){
    Pre-code;
    doCall();
    Post-code;
    }

    And you override the doCall() with just your Implementation or leave it as an empty default if you don't want to.

    Or
    private call(){
    preCall()
    doCall()
    postCall()
    }
    If you want to be really fancy and give access to all of it.
  • 2
    I absolutely believe that code should be designed in a way that's impossible to fuck up as much as possible, and OOP has enough solutions to cover the case you described. Calling the parent method as part of your own call should only be applied when you want to append your solution to the parent explicitly, it shouldn't be the default documented behavior ever. But even then, appending of parameters through call chains can be done by passing a list through a list of interceptors or just as a single context pass anyway. There's no reason to expose an override that requires calling It's parent explicitly other than being lazy
  • 1
    @Hazarth I agree. But for some reason, I’ve never seen APIs designed this way. The iOS’ UI sdk UIKit, Xamarin, WPF, and I believe Java Swing and Android SDK are all doing it “the wrong way”.
    I wonder why that is.
  • 1
    Ok I think I might have the answer:
    With your suggested approach, you could only extend the functionality once:
    You make a subclass and implement that preCall() method to extend the functionality of that object.
    But you can no longer make another subclass of your previous subclass, because you would need to ensure that the call() method calls preCall() of the previous subclass and also some extra preCall() from your new subclass. But you can’t ensure that.

    The only way to make possible an arbitrary chain of subclasses where each subclass is calling all the stuff from the previous implementations, is to use this “unsafe” approach.
  • 1
    I think the solution for this problem is composition over inheritance.

    And the world seems to agree. We are seeing more and more modern declarative UI frameworks which utilize composition as the main building blocks.
  • 1
    @Lensflare In my opinion, if you're extending a method of your own class that already extends a method of some framework or API, you're responsible for that. That wouldn't be an "documented requirement" you need to adhere to. It's your own extension that you're over-extending again for some reason. This is still a design issue, but it's a bit harder to see where it stems from.

    if you really do need an arbitrary chain of calls at the API/Framework layer, you need to do it using interceptors or listeners (so yes, composition) or something similar. Chain calls just shouldn't be hardcoded. I cringe when I see a framework that requires me to call the parent method when overriding... That's the same as saying "don't forget to call flush() at the end of a stream, except you're already in flush(), so call super.flush()" ... why? If it's a call that's required to be called 100% of the time, it has either no business being exposed or it's supposed to be nested in a control call...
  • 1
    @Hazarth yup. The chains of multiple subclasses are already present at the API/Framework level. This is typical as gui elements are designed something like that:
    Button extends InteractiveView extends View.
Add Comment