6

I've just installed Microsoft Code Contracts. It's a part of .NET Framework and Visual Studio add-on. It provides runtime checking and static checking of defined contracts.

The tool has four warning levels so I set up the highest.

I've declared classes designed to violate Liskov Substitution Principle.

public class Person
{
    protected int Age { get; set; }

    public Person(int age)
    {
        Contract.Requires(age > 0);
        Contract.Requires(age < 130);
        this.Age = age;
    }
}

public class Child : Person
{
    public Child(int age) : base(age)
    {
        Contract.Requires(age > 0); 
        Contract.Requires(age < Consts.AgeOfMajority);
        Contract.Requires(age < 130);
        this.Age = age;
    }
}

public static class Consts
{
    public readonly static int AgeOfMajority = 18;
}

LSP states:

if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program

In my example the violation would be this asignment: Person person = new Child(23);. We should be able to do this, but we can't because children can't be older than some age smaller than required by person class.

The result of analysis however is surprising CodeContracts: Checked 11 assertions: 11 correct. Is my example wrong or Code Contracts don't detect such things?

Arkadiusz Kałkus
  • 17,101
  • 19
  • 69
  • 108
  • 3
    I don't think this violates LSP as it stands. Your classes have no behavior and they have the same api. The only difference is that their rules of construction differ, but the way I interpret LSP it speaks from the point of view of a client using the class. Anyone dealing with `Persons` should never have to know about the rule that says stuff about a childs max age. In this case, they don't have to though. – sara Mar 05 '16 at 15:45
  • Note that the setter is unrestricted. Fix that, and you should get an error. – Kevin Mar 05 '16 at 16:23
  • @Kevin I've implemented full age property and added requires statements. Still no warnings. – Arkadiusz Kałkus Mar 05 '16 at 16:35
  • related https://stackoverflow.com/questions/5490824/should-constructors-comply-with-the-liskov-substitution-principle – alfC Aug 25 '21 at 02:48

3 Answers3

16

While it's true that LSP specifies a subtype can't place more restrictive preconditions on methods, this doesn't apply to constructors as you do not use constructors in a polymorphic way.

The contract violation would be new Child(23); which occurs before assigning to a Person.

So the example violation is wrong, it doesn't get as far as creating an instance of subtype S, let alone replacing it for T.

weston
  • 54,145
  • 21
  • 145
  • 203
  • Can you mention any articles saying LSP doesn't apply to constructors? I wouldn't be surprised if Child constructor could be different, for instance require some additional variable, but restricting inherited variable, well, for me it's a code smell. – Arkadiusz Kałkus Mar 05 '16 at 16:06
  • 2
    It's just logical. LSP talks about using instances of subtypes. If you can't create that instance of a subtype, you don't even get in to LSPs domain. – weston Mar 05 '16 at 16:25
  • the whole point of being able to substitute types for more derived types without caring is so you can use them polymorphically. what problems do you think could ever arise in code using a `Person` that would be because Age wasn't below 18? I think that if anything, the design smell here is that the interface for the class lies/suffers from primitive obsession. – sara Mar 05 '16 at 16:34
  • 1
    The smell right now is that you have no good reason for the Child class. It only exists for this precheck. – weston Mar 05 '16 at 16:43
  • @sara, I think you don't substitute types but *objects* for the discussion to make sense. – alfC Aug 25 '21 at 02:51
9

There's a famous example of LSP violation with the duck:

enter image description here

However it's not like we can violate it in a constructor. Let's say we have Duck and WildDuck classes:

public abstract class Duck
{
    public abstract string Quack();
    public double Weight { get; set; }

    public Duck(double weight)
    {
        Contract.Requires(weight > 0);
        this.Weight = weight;
    }
}

public class WildDuck : Duck
{
    public WildDuck(double weight)
        : base(weight)
    {
        Contract.Requires(weight > 0);
        this.Weight = weight;
    }

    public override string Quack()
    {
        return "wild quack";
    }
}

Now let's introduce ElectricDuck:

public class ElectricDuck : Duck
{
    public Battery Battery { get; set; }

    public override string Quack()
    {
        return "electric quack";
    }

    public ElectricDuck(double weight, Battery battery)
        : base(weight)
    {
        Contract.Requires(weight > 0);
        Contract.Requires(battery != null);
        this.Weight = weight;
        this.Battery = battery;
    }
}

public class Battery
{
    public bool IsEmpty { get; set; }
}

At firs glance it may seem it violates LSP because ElectricDuck requires more than WildDuck or abstract Duck. But it's not true as long as the ElectricDuck provides Quack method without additional requirements.

If ElectricDuck requires Battery to glow - it's perfectly correct from an LSP standpoint:

public void Glow()
{
    Contract.Requires(!this.Battery.IsEmpty);
}

LSP is violated when we add the requirement to inherited method:

public override string Quack()
{
    Contract.Requires(!this.Battery.IsEmpty);
    return "electric quack";
}

And this modification will cause CodeContracts to show a warning.

Arkadiusz Kałkus
  • 17,101
  • 19
  • 69
  • 108
2

I would say that liskov substitution governs the behavior of constructed instance of a class. So a properly constructed instance of a Child can be substituted for a Person with no problem.

You have constraints on how to construct a Child. I don't see the framework's not flagging this as a problem.

Robert Moskal
  • 21,737
  • 8
  • 62
  • 86