1

There are two other questions I've posted that dealt with specific cases of this:

Where does the Liskov Substitution Principle lie in a subclass passing extra arguments to similar, tightly-related callbacks?

Matching the superclass's constructor's parameter list, is treating a null default value as a non-null value within a constructor a violation of LSP?

but these have kind of left me a little lost on the general case. A basic summary of what I've gotten out of the answers to these questions is as follows:

The Liskov Substitution Principle does not apply to constructors; it only applies post-construction. You can change parameter lists all you want, and you can even make matching parameters exhibit very different behaviors.

An exception to this rule is that if a callback is passed in to the constructor of both the base class and the superclass, and if it cannot be set at a later time, then the subclass's version, in normal situations, must be backwards-compatible with superclass's version. The reason for this is that, in normal situations, outside code cannot otherwise enter into a state in which it would behave in the same way.

Even though this statement doesn't necessarily contradict itself, it comes close, yet only on a halfway fine-grained point. So at this point, it's probably a good idea for me to ask about the subprinciple of LSP that deals specifically with constructors, particularly with their parameter lists.

What is the general application of LSP here?


Example

Consider the following example, which only illustrates certain specific points (this question is still about the general case though, not this example):

BasicButton:

public class BasicButton extends Sprite
{
    private var m_fOnClick:Function;
    private var m_fOnPress:Function;
    private var m_fOnRelease:Function;

    private var m_iColorPressed:uint;
    private var m_iColorReleased:uint;

    public function BasicButton(pColorPressed:uint, pColorReleased:uint, pOnClick:Function
            = null, pOnPress:Function = null, pOnRelease:Function = null)
    {
        m_iColorPressed = pColorPressed;
        m_iColorReleased = pColorReleased;
        m_fOnClick = pOnClick;
        m_fOnPress = pOnPress;
        m_fOnRelease = pOnRelease;
        drawBackground(pColorReleased);
    }

    private function drawBackground(pColor:uint):void
    {
        // completely fill the entire rectangular area of the button with one solid color
    }

    .
    .
    .
        // when the button has been pressed:
        if (m_fOnPress)
        {
            m_fOnPress();
        }
    .
    .
    .
}

DirectionalButton:

// This class illustrates several different points.
public final class DirectionalButton extends BasicButton
{
    private var m_eDirection:int;

    private var m_fOnPress:Function;

    // Multiple parameters are omitted.  Two are just uint configurations to decide a color
    // with, and one is a callback that, if a non-null value had been included, would have
    // interacted directly with outside code.  Furthermore the pOnPress callback is treated
    // differently throughout this class.  Finally, pOnPress and pOnRelease are no longer
    // optional.
    public function DirectionalButton(pDirection:int, pOnPress:Function,
            pOnRelase:Function)
    {
        m_eDirection = pDirection;
        m_fOnClick = pOnClick;

        // pOnPress is nullified; the superclass won't even try to call it.
        // The colors are hard-coded.
        super(0x858585, 0xCCCCCC, null, onPress, pOnRelease); 

        addArrowSprite(pDirection);
    }

    private function addArrowSprite(pDirection:int):void
    {
        // Add a new Sprite instance as a child, which will take the form of an arrow
        // pointing in the specified direction, using a different color than the
        // background.  Whereas the you could always assume that the superclass maintained
        // one color throughout at all times, this assumption will now be broken in the
        // subclass.  That means that, depending on how you interpret things, the
        // background color specified in the superclass's constructor uses either differing
        // or equivalent behavior.
    }

    private function onPress():void
    {
        m_fOnPress(m_eDirection); // pOnPress from before has had its parameter
                                  // list interfered with.  Also it is now assumed to be
                                  // non-null.
    }
}

Remember that this example is still only dealing with the constructors' parameters, not with regular public functions or anything. Furthermore you have several different so-called "properties" that are suppliable to the constructors, but which have no way to be configured afterward.

Whether dealing with these sorts of cases, or with different types of cases, what is the LSP rule about constructors, especially with their parameter lists?

  • 4
    I'd say LSP talks about properties of objects, the constructor however is called while bringing the object into existence and isn't part of "property provable about objects" ... but others might have better supported answers :-) – johannes Jan 21 '15 at 14:52
  • 3
    Reference to the actual paper: reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.pdf – dietbuddha Aug 2 '15 at 22:26
3

It depends on what language you're working with.

Let's look at the informal definition of the Liskov Substitution Principle (herafter LSP):

You should be able to use an instance of a subtype anywhere you could use the base type.

That only tangentially impacts constructors. LSP doesn't particularly care how instances are created, only that once created they play nice. Constructors obviously can impact if the instances play nice when used, but that's not really different from other object state impacting their behavior.

In a few more esoteric languages though, the types (and their constructors) can be treated like objects. Having the constructors behave differently in that sort of context would be a violation of LSP, but of the type objects, not the actual class instances.

  • 1
    For example, in Ruby, classes are themselves objects. And there are no constructors, only methods. So, in theory, instances of subclasses of Class should be substitutable for instances of Class … but Ruby doesn't allow Class to be subclassed anyway, so the problem doesn't even arise. (Plus, classes aren't types in Ruby, so instances of subclasses aren't necessarily instances of subtypes.) – Jörg W Mittag Jan 21 '15 at 17:55
  • Thanks! Where does the exception above fit in? – Panzercrisis Jan 21 '15 at 22:03
  • 1
    @Panzercrisis - to be honest, it's not entirely clear. But if I understand properly, I don't agree that it's an exception. If you have a callback defined in both places then any method that satisfies the callback is satisfying the contract stipulated by LSP. It's like saying you can't pass 4 into the subtype and 2 into the basetype because then it will do different things. While that might not be a good idea, there's no clear substitute-ability violation going on there. – Telastyn Jan 21 '15 at 22:10
  • The exception I was thinking of was basically the same thing as the way pOnClick is handled in the two code snippets. It's only passed in during construction, never post-construction, but it'll either be called with one argument, be called with no arguments, or just not be called in later code, depending on what's passed to which constructor. That doesn't change the way outside code interfaces with member functions or variables post-construction, but it does relate somewhat to the way outside code has to define the callback itself. – Panzercrisis Jan 22 '15 at 21:57
  • If that's not an exception, there's probably not too much that is; but if it is an exception, it makes the line in the dust hard to see for me personally. – Panzercrisis Jan 22 '15 at 22:01

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.