Чем принцип подстановки Лискова отличается от нормального наследования?

Пытаюсь понять принцип подстановки Лискова. но я не могу определить, чем принцип подстановки Лисков отличается от нормального наследования. Ниже приведен код о нормальном наследовании. Что мне делать с приведенным ниже кодом, чтобы сказать, что мой код соответствует принципу замены Лискова

    public class ClassA
    {
        public virtual void MethodA()
        {
            Console.WriteLine("-----------------ClassA.MethodA---------------------");
        }

        public virtual void MethodB()
        {
            Console.WriteLine("-----------------ClassA.MethodB---------------------");
        }
    }

    public class ClassB: ClassA
    {
        public override void MethodA()
        {
            Console.WriteLine("-----------------ClassB override ClassA.MethodA---------------------");
        }
    }

1 ответ

Решение

Вот общее определение:

https://www.tomdalling.com/blog/software-design/solid-class-design-the-liskov-substitution-principle/

Принцип подстановки Лискова (LSP): функции, использующие указатели на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.

Вот более строгое объяснение:

https://en.wikipedia.org/wiki/Liskov_substitution_principle

..принцип замещения Лискова (LSP) - это конкретное определение отношения подтипов, называемое (сильным) поведенческим подтипом, которое первоначально было представлено Барбарой Лисков в выступлении на конференции 1987 года под названием Абстракция данных и иерархия. Это семантическое, а не просто синтаксическое отношение, поскольку оно призвано гарантировать семантическую совместимость типов в иерархии, в частности типов объектов. Барбара Лисков и Жаннетт Винг кратко описали принцип в статье 1994 года следующим образом:

Требование подтипа:

Let ϕ ( x ) be a property provable about objects x of type T. Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.

Принцип Лискова налагает некоторые стандартные требования к сигнатурам, которые были приняты в новых объектно-ориентированных языках программирования (обычно на уровне классов, а не типов; см. Различие между номинальными и структурными подтипами):

  • Контравариантность аргументов метода в подтипе.
  • Ковариация возвращаемых типов в подтипе.
  • Никакие новые исключения не должны генерироваться методами подтипа, за исключением случаев, когда эти исключения сами являются подтипами исключений, генерируемых методами супертипа.

Помимо требований к сигнатуре, подтип должен соответствовать ряду поведенческих условий. Они подробно описаны в терминологии, напоминающей методологию проектирования по контракту, что приводит к некоторым ограничениям на то, как контракты могут взаимодействовать с наследованием:

  • Предпосылки нельзя усилить в подтипе.
  • Постусловия не могут быть ослаблены в подтипе.
  • Инварианты супертипа должны сохраняться в подтипе.
  • Ограничение истории ("правило истории"). Считается, что объекты можно изменять только с помощью их методов (инкапсуляции). Поскольку подтипы могут вводить методы, которых нет в супертипе, введение этих методов может позволить изменения состояния в подтипе, которые недопустимы в супертипе. Ограничение истории запрещает это.

В: Соответствует ли ваш пример?

A: Я так не думаю. Потому что A.MethodA() семантически "отличается" от B.MethodA(). ClassB не проходит тест на утку.

Предлагаю контрпример:

public class ClassA {
  public virtual void MethodA() {
    Console.WriteLine("ClassA.MethodA");
  }

  public virtual void MethodB(){
    Console.WriteLine("ClassA.MethodB");
  }
}

public class ClassC: ClassA {
  public void MethodC() {
    Console.WriteLine("ClassC.MethodC");
  }
}

Это также отличный пример того, почему LSP НЕ "то же самое, что" наследование.

Другие вопросы по тегам