Как соблюдать Принцип замещения Лискова (LSP) и по-прежнему пользоваться полиморфизмом?

LSP говорит: "Производные типы не должны изменять поведение базовых типов", другими словами "Производные типы должны быть полностью заменены для базовых типов".

Это означает, что если мы определяем виртуальные методы в наших базовых классах, мы нарушаем этот принцип.

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

Другими словами, если мы используем полиморфизм, мы нарушили LSP!

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

Итак, как разработать приложение, которое соответствует этому принципу, когда вам нужно наследование, и вы также хотите воспользоваться полиморфизмом? Я в замешательстве!

См. пример отсюда: http://www.oodesign.com/liskov-s-substitution-principle.html

Ответ 1

LSP говорит, что вы должны иметь возможность использовать производный класс так же, как вы используете его суперкласс: "объекты в программе должны быть заменены экземплярами своих подтипов, не изменяя правильность этой программы". Классическое наследование, которое нарушает это правило, выводит квадратный класс из класса Rectangle, поскольку первый должен иметь Height = Width, а последний может иметь Height != Width.

public class Rectangle
{
    public virtual Int32 Height { get; set; }
    public virtual Int32 Width { get; set; }
}

public class Square : Rectangle
{
    public override Int32 Height
    {
        get { return base.Height; }
        set { SetDimensions(value); }
    }

    public override Int32 Width
    {
        get { return base.Width; }
        set { SetDimensions(value); }
    }

    private void SetDimensions(Int32 value)
    {
        base.Height = value;
        base.Width = value;
    }
}

В этом случае поведение свойств Width и Height изменилось, и это является нарушением этого правила. Возьмем вывод, чтобы увидеть, ПОЧЕМУ изменилось поведение:

private static void Main()
{
    Rectangle rectangle = new Square();
    rectangle.Height = 2;
    rectangle.Width = 3;

    Console.WriteLine("{0} x {1}", rectangle.Width, rectangle.Height);
}

// Output: 2 x 2

Ответ 2

У Барбары Лисков есть хорошая статья Абстракция и иерархия данных, где она специально затрагивает полиморфное поведение и конструкции виртуального программного обеспечения. После прочтения этой статьи вы можете видеть, что она глубоко описывает, как программный компонент может достичь гибкости и модульности от простых полиморфных вызовов.

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

Ключевое слово здесь неожиданно, поскольку оно может описывать любые свойства вашей программы (правильность, выполняемая задача, возвращаемая семантика, временно и т.д.). Таким образом, создание методов virtual не означает само по себе нарушение LSP

Ответ 3

Я думаю, что принцип замещения Лискова (LSP) в основном связан с перемещением реализации функций, которые могут отличаться от классов children, и оставлять родительский класс как можно более общим.

Итак, что бы вы ни изменили в дочернем классе, он не нарушает принцип замены Лискова (LSP), если это изменение не заставляет вас изменять код в родительском классе.

Ответ 4

"Производные типы не должны изменять поведение базовых типов" означает, что должно быть возможно использовать производный тип, как если бы вы использовали базовый тип. Например, если вы можете позвонить x = baseObj.DoSomeThing(123), вы также должны будете позвонить x = derivedObj.DoSomeThing(123). Полученный метод не должен генерировать исключение, если базовый метод этого не сделал. Код, использующий базовый класс, должен хорошо работать с производным классом. Он не должен "видеть", что использует другой тип. Это не означает, что производный класс должен делать то же самое; это было бы бессмысленно. Другими словами, использование производного типа не должно прерывать код, который работал плавно, используя базовый тип.

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

logger.WriteLine("hello");

Вы можете использовать инъекцию конструктора в классе, который должен создавать журналы. Теперь вместо того, чтобы передавать его в консольный логгер, вы передаете ему регистратор файлов, полученный из консольного регистратора. Если регистратор файлов генерирует исключение: "Вы должны указать номер строки в строке сообщения", это приведет к потере LSP. Однако не проблема, что запись идет в файл вместо консоли. То есть если регистратор показывает то же поведение для вызывающего, все в порядке.


Если вам нужно написать код, похожий на следующий, то LSP будет нарушен:

if (logger is FileLogger) {
    logger.Write("10 hello"); // FileLogger requires a line number

    // This throws an exception!
    logger.Write("hello");
} else {
    logger.Write("hello");
}

Кстати: ключевое слово new не влияет на полиморфизм, вместо этого он объявляет совершенно новый метод, который имеет то же имя, что и метод в базовом типе, но не связан с ним. В частности, нельзя назвать его базовым типом. Чтобы полиморфизм работал, вы должны использовать ключевое слово override, и метод должен быть виртуальным (если вы не реализуете интерфейс).

Ответ 5

Подтипы должны быть заменены базовыми типами.

В терминах контактов.

Производный класс может заменить предварительное условие базового класса для того же или более слабого и пост-состояния для того же или большего.

Ссылка

Ответ 6

Чтобы полиморфизм работал, необходимо соблюдать LSP. Прекрасным способом его разбить было бы введение методов в производный тип, которые не относятся к базовому типу. В этом случае полиморфизм не может работать, потому что эти методы недоступны в базовом типе. У вас может быть другая реализация подтипа метода, в то же время придерживаясь как полиморфизма, так и LSP.