Разница между принципом замещения IS-A и Лисков?

Мне просто интересно, есть ли разница между IS-A (терминология UML и ООП) и принципом замещения Лискова (LSP)?

Собственно, оба говорят о наследстве. Так в чем же основное отличие на практике?


person Ahmad Payan    schedule 30.08.2017    source источник


Ответы (3)


Оба термина в конечном итоге описывают одну и ту же «концепцию».

Принцип подстановки Лисков говорит вам: отношение наследования между классами B (базовый) и C (дочерний) правильное, когда каждое и любое использование некоторого объекта типа B... может быть заменено объект типа С.

Это означает: B определяет API и публичный контракт, а C также должен поддерживать эти свойства!

И IS-A сводится к тому же: некоторый объект C является также B.

«Разница» в том, что LSP дает вам точные правила, которые вы можете проверить. В то время как IS-A — это скорее «наблюдение» или выражение намерения. Как в: вы выражаете, что вы желаете, чтобы класс C IS-A B.

Другими словами: если вы не знаете, как правильно использовать наследование, IS-A не поможет вам написать правильный код. В то время как LSP ясно говорит вам, что что-то вроде:

class Base { int foo(); }
class Child extends Base { @Override double foo(); }

недействителен. Согласно LSP, вы можете только расширять аргументы метода и ограничивать возвращаемые значения.

int iValue = someBase.foo();

нельзя заменить на

int iValue = someChild.foo();

потому что результат метода foo() расширен.

И последнее соображение: многие люди думают, что C IS-A B это то же самое, что и просто записать Child extends Base. Да. Но это только сообщает компилятору, что C расширяет B. Это не означает, что методы, которые вы используете в C, будут следовать LSP и, таким образом, превратят C в настоящего допустимого потомка B.

C IS-A B требует больше, чем "C расширяет B". Чтобы быть действительно действительным, LSP должен поддерживаться!

person GhostCat    schedule 30.08.2017
comment
Спасибо за ответ. Так что было бы лучше рассмотреть принцип LSP вместо IS-A, чтобы реализовать наследование? - person Ahmad Payan; 30.08.2017
comment
Как сказано: IS-A не помогает реализовать наследование. LSP дает вам четкие правила, которым вы можете следовать (соответственно: которые вы должны знать при определении API/публичных контрактов ваших классов в вашей модели наследования). - person GhostCat; 30.08.2017

Is-A/Has-A касается того, следует ли использовать наследование. Является ли LaserCat типом лазера или у него должно быть только поле для лазера? LSP — это особая проблема, на которую следует обратить внимание, если вы используете наследование определенным образом.

Хорошее использование наследования — иметь Animal a1; указывая на кошку или собаку, используя a1.speed() (*). LSP говорит, что функции скорости Cat и Dog должны использовать одни и те же единицы измерения. Точно так же a1.setWeight для Cats не может допускать отрицательных весов, но Dogs изменяет их на 0. LSP — это согласованность, когда вы можете вызывать любую функцию. На самом деле это довольно очевидно, если вы уже знаете Животное a1; трюк, который трудно.

Для контраста предположим, что у вас есть отдельные кошки и собаки. Если на самом деле скорости измеряются по-разному, это нормально, если для кошек используется метрика, а для собак — английский. Если Cats и Dogs наследуют от Animal, но вы никогда не используете трюк «a1 = Cat or Dog», все равно нормально. c1.speed() точно в метрике, d1.speed() явно в милях в час. Но если у вас есть функция animalRace(Animal a1), у вас есть проблема.

Разница также в тоне. Is-a/has-a — простой совет для начинающих. LSP взят из статьи 30-летней давности, написанной для докторов наук. Уравнения, которые он использует, предназначены для выпускников, специализирующихся в области компьютерных наук. В нем используются условия Pre и Post, которые в то время были обычными и хорошо известными терминами. «Подстановка» — хороший математический термин, но сегодня мы бы сказали просто «базовый класс, который будет указывать на любой подкласс».

(*) Более подробно: у нас есть суперкласс Animal и подклассы Cat и Dog. У животных есть функция скорости заглушки, и Кошка и Собака переопределяют ее. a1.speed() ищет правильный. Реальным примером этого является массив Animals, который действительно содержит Cats и Dogs. Или функция с входом Animal, ожидающая Cat или Dog.

(то же *) Часто базовый класс является абстрактным — мы никогда не создадим объект Animal. Но трюк тот же, если у нас есть суперкласс Toaster и подкласс DeluxeToaster. Все, что берет тостер, может брать все, что является тостером.

person Owen Reynolds    schedule 04.04.2019

Наследование является чисто синтаксическим отношением, тогда как Подстановка Лисков также является семантическим отношением.

Синтаксис прост: вы узнаете, как объявить один класс дочерним по отношению к другому, а компилятор сообщит вам, написали ли вы корректный код. Если код компилируется, вы создали отношение наследования (IS-A).

Семантика сложнее: она определяет, что код означает для клиентов. Семантическое значение часто включает такие вещи, как документация. В популярных ООП-языках компилятор не может сказать вам, соответствует ли код своему предполагаемому значению или нарушает его. Тут в дело вступает Лисков.

person jaco0646    schedule 29.12.2020