Принцип подстановки Лисков

171
08 апреля 2022, 15:40

Принцип, описывает 3 правила:

Предусловия (Preconditions) не могут быть усилены в подклассе. Другими словами подклассы не должны создавать больше предусловий, чем это определено в базовом классе, для выполнения некоторого поведения

Постусловия (Postconditions) не могут быть ослаблены в подклассе. То есть подклассы должны выполнять все постусловия, которые определены в базовом классе.

Инварианты (Invariants) — все условия базового класса - также должны быть сохранены и в подклассе

Вроде, все логично, но не понимаю, что в таком случае вооще можно делать при наследовании, что бы не нарушить данный принцип...

Например, вот пример с метанита: Усиление предусловия:

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;
    }
}

Ослабление постусловия:

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;
    }
}

Что-то не понимаю, а что если в наследних мне действительно нужно изменить некоторое поведение?

Или вот есть интерфейс и 10-ок классов реализует этот интерфейс => они могут реализовать различное поведение, так как первоначальной реализации нет.

Answer 1

Этот принцип уже обсужден много раз (например, раз, два, три)

В общем случае он звучит как

Функции, которые используют ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом

Каким образом этого можно добиться?

Допустим, у вас есть интерфейс. И у этого интерфейса есть описание (образно говоря, контракт, то есть описание, что должны делать члены этого интерфейса), например вот IList, возьмем его метод Add

Добавляет элемент в коллекцию IList.

Параметры value Object Объект, добавляемый в коллекцию IList.

Возвращаемое значение Int32 Позиция, в которую вставлен новый элемент, или значение -1, если элемент не вставлен в коллекцию.

Здесь явно прописано поведение реализации. Таким образом, не важно, какую вы используете реализацию списка, метод должен принимать объект и возвращать индекс.

Как можно сломать предусловие? Очень просто

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

В интерфейсе явно не прописано, что можно добавлять каие то особенные элементы, потому, клиенты этого интерфейса очень удивятся, если в этот необобщенный список можно будет добавлять не все объекты, что захочется.

Как сломать постусловие? Очень просто

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

В интерфейсе четко указано, что при успешной вставке в коллекцию, метод должен вернуть корректный индекс. Если мы нарушаем это правило - мы нарушаем контракт интерфйса.

Таким образом принцип подстановки Лисков говорит вам - вы можете реализовать как вы хотите ваши классы, но контракт интерфейса или базового класса вы должны сохранить. В таком случае, если существует уже написанный код, который оперирует с IList согласно контракту интерфейса, то вы можете подсунуть ему любую реализацию IList и код должен продолжать успешно работать.

READ ALSO
Как сформировать модель что б обращаться через свойства а не масcивы. С#

Как сформировать модель что б обращаться через свойства а не масcивы. С#

Хочу сформировать модель таким образом что б при возвращение на клиент я обращался через точку и свойство а не через номер в массивеПример

138
IoC в WPF по правилам MVVM

IoC в WPF по правилам MVVM

Постепенно начал изучать IoC и всю эту кухню и вот не как не могу понять, как работать с ними в WPF приложение по правилам MVVM

154
Положение окна WPF

Положение окна WPF

Окно WPF запускается с WindowState="Maximized"Если сразу после запуска посмотреть Left или Top окна то они будут равны -8

102
c# linq группировка по диапазону с условием

c# linq группировка по диапазону с условием

Имеется список со значениями координат:

100