Question

According to the Liskov principle, a construction like the one below is invalid, as it strengthens a pre-condition.

I know the example is pointless/nonsense, but when I last asked a question like this, and used a more elaborate code sample, it seemed to distract people too much from the actual question.

//Data models
abstract class Argument
{
    protected $value = null;
    public function getValue()
    {
        return $this->value;
    }
    abstract public function setValue($val);
}
class Numeric extends Argument
{
    public function setValue($val)
    {
        $this->value = $val + 0;//coerce to number
        return $this;
    }
}
//used here:
abstract class Output
{
    public function printValue(Argument $arg)
    {
        echo $this->format($arg);
        return $this;
    }
    abstract public function format(Argument $arg);
}
class OutputNumeric extends Output
{
    public function format(Numeric $arg)//<-- VIOLATION!
    {
        $format = is_float($arg->getValue()) ? '%.3f' : '%d';
        return sprintf($format, $arg->getValue());
    }
}

Why would this kind of "violation" be considered harmful? So much so that some languages, like the one I used in this example (PHP), don't even allow this?

I'm not allowed to strengthen the type-hint of an abstract method but, by overriding the printValue method, I am allowed to write:

class OutputNumeric extends Output
{
    final public function printValue(Numeric $arg)
    {
        echo $this->format($arg);
    }
    public function format(Argument $arg)
    {
        $format = is_float($arg->getValue()) ? '%.3f' : '%d';
        return sprintf($format, $arg->getValue());
    }
}

But this would imply repeating myself for each and every child of Output, and makes my objects harder to reuse.

I understand why the Liskov principle exists, don't get me wrong, but I find it somewhat difficult to fathom why the signature of an abstract method in an abstract class has to be adhered to so much stricter than a non-abstract method.

Why I'm not allowed to hind at a child class, in a child class?

The way I see it, the child class OutputNumeric is a specific use-case of Output, and thus might need a specific instance of Argument, namely Numeric. Is it really so wrong of me to write code like this, when I am allowed to write this:

abstract class Output
{
    public function printValue(Argument $arg)
    {
        echo $this->format($arg);
    }
    public function(Argument $arg)
    {
        throw new RuntimeException(__METHOD__. ' Has to be overridden by child class');
    }
}
class OutputNumeric extends Output
{
    final public function format(Numeric $arg)
    {
        $format = is_float($arg->getValue()) ? '%.3f' : '%d';
        return sprintf($format, $arg->getValue());
    }
}

This sort of acchieves the same thing, but it's just hacky and, to my eye, horrid code...

Was it helpful?

Solution

You aren't just breaking LSP here, you're breaking the basic concept of inheritance. If you have a format(Argument $arg) in your base type, then you must have something which can match it in the derived type.

Consider that if your example were allowed, what would happen if you attempted to pass a string to OutputNumeric::format? If you had specific type information and you knew you were passing it to an OutputNumeric, it would be a type error. If you didn't and you were passing it to an Output, it wouldn't be a type error, but where could it dispatch to?

This isn't so much a violation of LSP as it is a violation of the basic contract set out by that superclass, and you can't go around violating those or suddenly your compiler can get into situations where it can't tell you that you're wrong, but it also has nowhere to dispatch the function to!

What could be a violation of LSP in this case would be if you had a NumericOutput which accepted any natural number, but also a subclass which would fail on any number higher than n. The class's contract (or "interface") hasn't changed, but the preconditions have.

Licensed under: CC-BY-SA with attribution
scroll top