The Liskov substitution principle

Principle, describes 3 rules:

Prerequisites cannot be reinforced in a subclass. By others in other words, subclasses should not create more preconditions than this defined in the base class, to perform some behavior

Postconditions cannot be relaxed in a subclass. That is, subclasses must fulfill all the postconditions that are defined in the base class.

Invariants (Invariants) - all base class conditions-must also be met can also be stored in the{[3] subclass]}

It seems that everything is logical, but I do not understand what in this case you can do with inheritance, so as not to violate this principle...

For example, here is an example from metanite: Strengthening the precondition:

class Account
{
    public int Capital { get; protected set; }

    public virtual void SetCapital(int money)
    {
        if (money < 0)
            throw new Exception("Нельзя положить на счет меньше 0");
        this.Capital = money;
    }
}

class MicroAccount : Account
{
    public override void SetCapital(int money)
    {
        if (money < 0)
            throw new Exception("Нельзя положить на счет меньше 0");

        if (money > 100)
            throw new Exception("Нельзя положить на счет больше 100");

        this.Capital = money;
    }
}

Relaxing the postcondition:

class Account
{
    public virtual decimal GetInterest(decimal sum,  int month, int rate)
    {
        // предусловие
        if (sum < 0 || month >12 || month <1 || rate <0)
            throw new Exception("Некорректные данные");

        decimal result = sum;
        for (int i = 0; i < month; i++)
            result += result * rate  / 100;

        // постусловие
        if (sum >= 1000)
            result += 100; // добавляем бонус

        return result;
    }
}

class MicroAccount : Account
{
    public override decimal GetInterest(decimal sum, int month, int rate)
    {
        if (sum < 0 || month > 12 || month < 1 || rate < 0)
            throw new Exception("Некорректные данные");

        decimal result = sum;
        for (int i = 0; i < month; i++)
            result += result * rate /100;

        return result;
    }
}

Something I don't understand, but what if in the legacy I really need to change some behavior?

Or here there is an interface and 10-ok classes implements this interface => they can implement different behavior, since there is no initial implementation.

 3
Author: iluxa1810, 2020-02-09

1 answers

This principle has already been discussed many times (for example, times, two, three)

In general, it sounds like

Functions that use references to base classes should be able to use objects of derived classes without knowing about it

How can this be achieved?

Let's say you have an interface. And this interface has a description (figuratively speaking, a contract, that is, a description of what the members should do for example, here is IList, take its Add

Adds an item to the IList collection.

Parameters value Object An object to add to the IList collection.

Return value Int32 The position at which the new item is inserted, or the value -1 if the item is not inserted in the collection.

The implementation behavior is explicitly spelled out here. So it doesn't matter which list implementation you use., the method must accept an object and return an index.

How can a precondition be broken? Very simple

public int Add (object value)
{
    if (!(value is MyClass)) throw new ArgumentException();
    .....
}

The interface does not explicitly state that you can add some special elements, because the clients of this interface will be very surprised if you can not add all the objects that you want to this uncomplicated list.

How to break a postcondition? Very simple

public int Add (object value)
{        
    .....
    // вставка в коллекцию
    return -1;
}

The interface clearly states that when successfully inserted into the collection, the method should return the correct index. If we break this rule, we break the interfax contract.

So the Lisk substitution principle tells you - you can implement as you want your classes, but you must keep the interface or base class contract. In this case, if there is already written code that operates with IList according to the interface contract, then you can slip it any implementation of IList and the code should continue to work successfully.

 7
Author: tym32167, 2020-02-10 04:47:26