Принцип подстановки Лискова - нет переопределения / виртуальных методов? - PullRequest
54 голосов
/ 14 ноября 2009

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

Полагаю, это будет означать, что когда метод определен в базовом классе, он никогда не должен переопределяться в производном классе - поскольку замена базового класса вместо производного класса даст другие результаты. Я предполагаю, что это также означало бы, что иметь (не чистые) виртуальные методы - это плохо?

Думаю, у меня неправильное понимание принципа. Если я этого не сделаю, я не понимаю, почему этот принцип является хорошей практикой. Может кто-то объяснить это мне? Спасибо

Ответы [ 5 ]

52 голосов
/ 14 ноября 2009

Методы переопределения подклассов в базовом классе полностью разрешены по принципу подстановки Лискова.

Возможно, это слишком сильно упрощает, но я помню это как "подкласс не должен требовать больше ничего и обещать не меньше"

Если клиент использует суперкласс ABC с методом something(int i), тогда клиент должен иметь возможность заменить любой подкласс ABC без проблем. Вместо того, чтобы думать об этом с точки зрения переменных типов, возможно, подумайте об этом с точки зрения предусловий и постусловий.

Если наш something() метод в вышеприведенном базовом классе ABC имеет смягченное предварительное условие, разрешающее любое целое число, то все подклассы ABC должны также разрешать любое целое число. Подклассу GreenABC не разрешается добавлять дополнительное предварительное условие к методу something(), для которого требуется, чтобы параметр был положительным целым числом. Это нарушило бы принцип подстановки Лискова (т. Е. Требовало большего). Таким образом, если клиент использует подкласс BlueABC и передает отрицательные целые числа в something(), клиент не сломается, если нам нужно переключиться на GreenABC.

И наоборот, если базовый метод ABC class something() имеет постусловие - например, гарантируя, что он никогда не вернет значение ноль - тогда все подклассы также должны подчиняться этому же постусловию, или они нарушают принцип подстановки Лискова ( т.е. обещать меньше).

Надеюсь, это поможет.

10 голосов
/ 16 октября 2013

Есть один популярный пример, который говорит, что если он плавает как утка, кряк любит утку, но требует батарей, то это нарушает принцип замещения Лискова.

Проще говоря, у вас есть базовый класс Duck, который кем-то используется. Затем вы добавляете иерархию, вводя PlasticDuck с такими же переопределенными поведениями (как плавание, кряканье и т. Д.), Как у утки, но требуются батареи для имитации этого поведения. По сути, это означает, что вы вводите дополнительные предварительные условия в поведение подкласса, чтобы требовать от батарей того же поведения, что было ранее в классе базовых уток без батареек. Это может застать врасплох потребителя вашего класса Duck и может нарушить функциональность, основанную на ожидаемом поведении класса Base Duck.

Вот хорошая ссылка - http://lassala.net/2010/11/04/a-good-example-of-liskov-substitution-principle/

7 голосов
/ 14 ноября 2009

Нет, это говорит о том, что вы должны иметь возможность использовать производный класс так же, как и его базовый. Есть много способов переопределить метод, не нарушая его. Простой пример, GetHashCode () в C # лежит в основе для ВСЕХ классов, и все же ВСЕ из них могут использоваться как «объект» для вычисления хеш-кода. Насколько я помню, классическим примером нарушения правила является производное Square от Rectangle, поскольку Square не может иметь ширину и высоту - поскольку установка одного изменит другое, и, следовательно, он больше не соответствует правилам Rectangle. Однако вы можете по-прежнему иметь базовую форму с помощью .GetSize (), так как ВСЕ фигуры могут сделать это - и, таким образом, любая производная форма может быть заменена и использована в качестве фигуры.

3 голосов
/ 28 апреля 2011

Переопределение разрывов Принцип подстановки Лискова, если вы изменяете любое поведение, определенное базовым методом. Это означает, что:

  1. Самая слабая предпосылка для дочерний метод должен быть не сильнее чем для базового метода.
  2. Постусловие для дочернего метода подразумевает постусловие для родительский метод. Где постусловие состоит из: а) все стороны эффекты, вызванные выполнением метода и b) тип и значение возвращаемого выражения.

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

Если эти правила не соблюдаются, класс нарушает LSP. Классическим примером является следующая иерархия: класс Point(x,y), класс ColoredPoint(x,y,color), который расширяет Point(x,y), и переопределенный метод equals(obj) в ColoredPoint, который отражает равенство по цвету. Теперь, если у каждого есть экземпляр Set<Point>, он может предположить, что две точки с одинаковыми координатами равны в этом наборе. Что не так с переопределенным методом equals, и, в общем, просто невозможно расширить экземплярный класс и добавить аспект, используемый в методе equals, не нарушая LSP.

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

1 голос
/ 15 ноября 2009

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

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

Если клиенту, кажется, нужно выяснить класс переданного экземпляра, то вас ждет кошмар обслуживания, поскольку вам действительно нужно просто добавлять новые классы в рамках ваших усилий по обслуживанию, а не изменять существующий рутина. (см. также OCP )

...