1

Take the following examples, which have been abbreviated:

BasicButton:

public class BasicButton
{
    private var m_fOnClick:Function;

    public function BasicButton(pOnClick:Function)
    {
        m_fOnClick = pOnClick;
    }

    .
    .
    .
        // when the button has been clicked:
        m_fOnClick();
    .
    .
    .
}

DirectionalButton:

public final class DirectionalButton extends BasicButton
{
    private var m_eDirection:int;

    private var m_fOnClick:Function;

    public function DirectionalButton(pDirection:int, pOnClick:Function)
    {
        m_eDirection = pDirection;
        m_fOnClick = pOnClick;
        super(onClick);
    }

    private function onClick():void
    {
        m_fOnClick(m_eDirection);
    }
}

Is messing with the callback's parameter list like this a violation of the Liskov Substitution Principle? I know the constructors don't violate it, according to the way the industry generally interprets that principle, but whether messing with the callback's parameter list constitutes a mutation of a common output or whatever seems to be a little less clear.

On the one hand, both classes use a function called pOnClick that is called each time they're clicked on, and the instantiating code is able to pass in whichever function it wants to be referred to by pOnClick. On the other hand, since constructors tend to be abstracted from LSP, these two pOnClick parameters in the two constructors could be interpreted as referring to very different things.

Even if the two pOnClick parameters are interpreted as two separate inputs, there's still some ambiguity as to how much the subclasses's pOnClick effectively overwrites or destroys the superclass's (in a strictly virtual, publicly exposed sense), and how much it instead simply passes - in a very virtual sense - the equivalent of null to the superclass's still existent pOnClick.

It's a little tough to find the exact words for what I'm describing, but in general, where does LSP lie in this? Is this a violation? If so, can it be remedied by simply rewriting DirectionalButton.onClick as follows?

private function onClick():void
{
    try
    {
        m_fOnClick(m_eDirection);
    }
    catch (e:Error)
    {
        m_fOnClick(); // This is not how I would set this up in real life.
    }
}
  • LSP is all about semantics. It's impossible to tell if you have a violation without defining the semantics first - for example, if you say that a BasicButton can do absolutely anything when clicked, then no subclass can possibly violate LSP. – Doval Jan 20 '15 at 22:58
3

The test is whether you can take code written to interact with the base class, and without modification, get it to work with an instance of the derived class instead. That means in most circumstances, your example would violate LSP.

However, in some circumstances it might not. If you're using a language like JavaScript that essentially ignores extra parameters, and that parameter is not semantically crucial, you could make a case that it doesn't violate LSP.

In a dynamically-typed language, you could make a case that your proposed fallback remedy doesn't violate LSP. However, I don't believe you could get something like that to compile in most statically-typed languages.

One remedy that would work in any language is to just change the base class to add the optional parameter. That feels kind of icky, though.

The easiest way to not accidentally break inheritance design principles is to not use inheritance. You could have your directional button class contain a basic button instead of inheriting from it, and forward any events with the parameter added. This is a good option if you never have to put a directional button into code that was originally designed to interact with a basic one.

Another very flexible option to pass arbitrary data back to a callback is to pass in a closure to the constructor instead.

  • Thanks! I guess that also means if the base class instead had three callbacks, pOnClick, pOnPressed, and pOnReleased, which could only be specfied as optional arguments to the constructor, and then if one of the subclasses turns around and only exposes two of those in its constructor (automatically making the other one null), that would also constitute a violation, right? The argument being not that the constructors have different parameter lists, but that in this case, the constructors would be the only way for outside code to set up the third callback. – Panzercrisis Jan 20 '15 at 23:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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