4

I have an event handler. It receives an event and needs to do some work on it. That work is split between common work - things that have to be done for all types of event handler - and specific work - things that only need to be done for a specific app.

There seem to be two approaches to implementing this design. I could write a non-virtual member function, that invokes a differently named pure virtual member function:

struct Base {
    virtual ~Base() = default;

    void onEvent(Event const& ev) {
        // do common stuff
        onEventImpl(ev);
    }
protected:
    virtual void onEventImpl(Event const& ) = 0;
};

or, I could have a single virtual member function and rely upon the derived classes to always call the Base class first:

struct Base {
    virtual ~Base() = default;

    virtual void onEvent(Event const& ev) {
        // do common stuff
    }
};

struct Derived : Base {
    void onEvent(Event const& ev) override {
        Base::onEvent(ev);
        // do specific stuff
    }
};

The former is safe, but involves having two of every function named basically the same.

The latter is error-prone in that the derived classes could forget to call the base class (although this error would be readily apparent from the first test - not the kind of subtle error that could creep up on you), but at least we just have the one onEvent() function.

What's the right approach to solving this problem? Is there a better option than these two?

1 Answer 1

4

Practically speaking, you can't enforce that the derived's override will invoke parent methods first. That isn't the OOP paradigm: derived methods get control first, on purpose, then do what they want, then call the parent method (if at all), then get control back again. An override is effectively a preempting patch, and that is by design.

So, your first approach (of the two) is the only way to enforce this behavior.

The cost of having a non-virtual method here is negligible in performance, and a minor inconvenience in design (two similar methods) that is well worth it to get the guarantees you're looking for without even requiring enforcement.

The first approach also keeps design requirements local to the design itself within the base class.

Btw, this pattern is not uncommon, of using multiple methods to accomplish a single design point (e.g. mixing virtual and non-virtual.)


The other way requires you to document some required/demanded behavior of the derived methods, possibly with no way to test or enforce the requirement. In the presence of a better alternative, the latter is surely a violation of a number of design principles.

KISS, Keep it simple, says why have a requirement documented in prose for humans to read and follow when you can just solve the problem directly in the code.

DRY, Don't repeat yourself, says solve the requirement in one place (the base class), not in multiple places (i.e. in each subclass).

Though we don't often talk about loose coupling with subclassing, clearly the latter form tightens coupling between base and subclasses, which is a tell/smell of sorts.


I think the first solution provides the guarantees you're looking for here and is pretty simple -- which is great, so I can't think of a better approach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.