Я слышал, что принцип замещения Лискова (LSP) является фундаментальным принципом объектно-ориентированного проектирования. Что это такое и каковы некоторые примеры его использования?
908
Я слышал, что принцип замещения Лискова (LSP) является фундаментальным принципом объектно-ориентированного проектирования. Что это такое и каковы некоторые примеры его использования?
Ответы:
Отличным примером, иллюстрирующим LSP (данный дядей Бобом в подкасте, который я недавно слышал), было то, как иногда что-то, что звучит правильно на естественном языке, не совсем работает в коде.
В математике а
Square
естьRectangle
. На самом деле это специализация прямоугольника. «Is» заставляет вас моделировать это с наследованием. Однако, если в коде, который вы сделали,Square
происходит отRectangle
, то aSquare
должен использоваться везде, где вы ожидаете aRectangle
. Это делает для некоторого странного поведения.Представьте, что у вас есть
SetWidth
иSetHeight
методы в вашемRectangle
базовом классе; это кажется совершенно логичным. Однако, если вашаRectangle
ссылка указывает на aSquare
, тогдаSetWidth
иSetHeight
не имеет смысла, потому что установка одного изменит другой в соответствии с ним. В этом случаеSquare
не проходит Лисковский тест с заменой,Rectangle
и абстракция наличияSquare
наследованияRectangle
является плохой.Вы все должны проверить другие бесценные мотивационные плакаты Принципов SOLID .
источник
Square.setWidth(int width)
была реализована следующим образом:this.width = width; this.height = width;
? В этом случае гарантируется, что ширина равна высоте.Принцип замещения Лискова (LSP, LSP) является концепцией в объектно-ориентированном программировании, которая гласит:
По сути, LSP - это интерфейсы и контракты, а также то, как решать, когда расширять класс, а не использовать другую стратегию, например композицию, для достижения своей цели.
Наиболее эффективный способ проиллюстрировать этот момент - « Head First OOA & D» . Они представляют сценарий, в котором вы являетесь разработчиком проекта по созданию платформы для стратегических игр.
Они представляют класс, представляющий доску, которая выглядит следующим образом:
Все методы принимают координаты X и Y в качестве параметров для определения положения плитки в двумерном массиве
Tiles
. Это позволит разработчику игры управлять юнитами на доске в течение игры.В книге далее изменяются требования, чтобы сказать, что структура игры должна также поддерживать 3D игровые поля, чтобы приспособиться к играм, в которых есть полет. Итак
ThreeDBoard
, введен класс, который расширяетсяBoard
.На первый взгляд это кажется хорошим решением.
Board
обеспечивает какHeight
иWidth
свойства иThreeDBoard
обеспечивает ось Z.Где это ломается, когда вы смотрите на всех других членов, унаследованных от
Board
. МетодыAddUnit
,GetTile
,GetUnits
и так далее, все принимать как X и Y параметры вBoard
классе , ноThreeDBoard
нужен параметр Z , а также.Таким образом, вы должны снова реализовать эти методы с параметром Z. Параметр Z не имеет контекста для
Board
класса, а унаследованные методыBoard
класса теряют свое значение. Единица кода, пытающаяся использоватьThreeDBoard
класс в качестве базового класса,Board
была бы очень неудачной.Может быть, мы должны найти другой подход. Вместо того, чтобы расширяться
Board
,ThreeDBoard
должен состоять изBoard
объектов. ОдинBoard
объект на единицу оси Z.Это позволяет нам использовать хорошие объектно-ориентированные принципы, такие как инкапсуляция и повторное использование, и не нарушает LSP.
источник
давайте сделаем простой пример на Java:
Плохой пример
Утка может летать, потому что это птица, но как насчет этого?
Страус - это птица, но он не может летать, класс Страус - это подтип класса Bird, но он не может использовать метод fly, это означает, что мы нарушаем принцип LSP.
Хороший пример
источник
Bird bird
. Вы должны бросить объект в FlyingBirds, чтобы использовать муху, что не очень хорошо, верно?Bird bird
, это означает, что он не может использоватьfly()
. Вот и все. ПередачаDuck
не меняет этот факт. Если клиент имеетFlyingBirds bird
, то, даже если он прошел,Duck
он всегда должен работать одинаково.LSP касается инвариантов.
Классический пример дается следующим объявлением псевдокода (реализации опущены):
Теперь у нас проблема, хотя интерфейс совпадает. Причина в том, что мы нарушили инварианты, вытекающие из математического определения квадратов и прямоугольников. Способ работы геттеров и сеттеров
Rectangle
должен удовлетворять следующему инварианту:Однако этот инвариант должен быть нарушен правильной реализацией
Square
, поэтому он не является допустимой заменойRectangle
.источник
У Роберта Мартина есть отличная статья о принципе замены Лискова . В нем обсуждаются тонкие и не очень тонкие способы нарушения принципа.
Некоторые важные части статьи (обратите внимание, что второй пример сильно сжат):
источник
Now the rule for the preconditions and postconditions for derivatives, as stated by Meyer is: ...when redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.
если предварительное условие дочернего класса сильнее предварительного условия родительского класса, вы не можете заменить дочернего элемента родителем, не нарушив этого предварительного условия. Отсюда и LSP.LSP необходим, когда некоторый код считает, что он вызывает методы типа
T
, и может неосознанно вызывать методы типаS
, гдеS extends T
(то естьS
наследует, наследует или является подтипом супертипаT
).Например, это происходит, когда функция с входным параметром типа
T
вызывается (то есть вызывается) со значением аргумента типаS
. Или, где идентификатор типаT
, присваивается значение типаS
.LSP требует, чтобы ожидания (то есть инварианты) для методов типа
T
(напримерRectangle
) не нарушались при вызове методов типаS
(напримерSquare
).Даже у типа с неизменяемыми полями все еще есть инварианты, например неизменяемые установщики Rectangle ожидают, что размеры будут изменены независимо, но неизменные установщики Square нарушают это ожидание.
LSP требует, чтобы у каждого метода подтипа
S
были контравариантные входные параметры и ковариантный выход.Контравариантный означает, что дисперсия противоречит направлению наследования, то есть тип
Si
каждого входного параметра каждого метода подтипаS
должен быть одинаковым или супертипом типаTi
соответствующего входного параметра соответствующего метода супертипа.T
,Ковариантность означает, что дисперсия находится в том же направлении наследования, то есть тип
So
выходных данных каждого метода подтипаS
должен быть одинаковым или подтипом типаTo
соответствующего выходного сигнала соответствующего метода супертипаT
.Это связано с тем, что если вызывающий объект
T
думает, что у него есть тип , что он вызывает методT
, то он предоставляет аргумент (ы) типаTi
и присваивает вывод типуTo
. Когда он фактически вызывает соответствующий методS
, тогда каждыйTi
входной аргумент назначаетсяSi
входному параметру, аSo
выходной - типуTo
. Таким образом , еслиSi
не контравариантен WRT кTi
, то подтипXi
-Какой не будет подтипомSi
-Не могли бы быть назначеныTi
.Кроме того, для языков (например, Scala или Ceylon), которые имеют аннотации на сайте определения параметров полиморфизма типов (т. Е. Обобщения), совместное или противоположное направление аннотации для каждого параметра типа этого типа
T
должно быть противоположным или одинаковым направлением. соответственно каждому входному параметру или выходу (каждого методаT
), который имеет тип параметра типа.Кроме того, для каждого входного параметра или выхода, который имеет тип функции, требуемое направление отклонения меняется на противоположное. Это правило применяется рекурсивно.
Подтип подходит для тех случаев, когда можно перечислить инварианты.
В настоящее время проводится много исследований о том, как моделировать инварианты, чтобы они обеспечивались компилятором.
Typestate (см. Стр. 3) объявляет и применяет инварианты состояния, ортогональные типу. Альтернативно, инварианты могут быть реализованы путем преобразования утверждений в типы . Например, чтобы утверждать, что файл открыт до его закрытия, File.open () может вернуть тип OpenFile, который содержит метод close (), недоступный в File. Крестики-нолики API , может быть еще одним примером применения печатать для обеспечения инвариантов во время компиляции. Система типов может быть даже полной по Тьюрингу, например, Scala . Языки с независимой типизацией и доказатели теорем формализуют модели типизации высшего порядка.
Из-за необходимости семантики абстрагироваться от расширения , я ожидаю, что использование типизации для моделирования инвариантов, то есть унифицированной денотационной семантики высшего порядка, превосходит Typestate. «Расширение» означает неограниченную пермутированную композицию несогласованного модульного развития. Поскольку мне кажется, что антитеза объединения и, следовательно, степеней свободы, иметь две взаимозависимые модели (например, типы и Typestate) для выражения общей семантики, которые не могут быть объединены друг с другом для расширяемой композиции. , Например, похожее на выражение выражение было унифицировано в областях подтипирования, перегрузки функций и параметрической типизации.
Моя теоретическая позиция заключается в том, что для существования знаний (см. Раздел «Централизация слепа и непригодна») никогда не будет общей модели, которая могла бы обеспечить 100% -ное покрытие всех возможных инвариантов на языке полного языка Тьюринга. Чтобы знания существовали, неожиданных возможностей много, то есть беспорядок и энтропия всегда должны увеличиваться. Это энтропийная сила. Чтобы доказать все возможные вычисления потенциального расширения, нужно заранее вычислить все возможные расширения.
Вот почему существует теорема Остановки, т. Е. Неразрешимо, завершается ли каждая возможная программа на языке Тьюринга. Можно доказать, что какая-то конкретная программа завершается (та, для которой все возможности были определены и вычислены). Но невозможно доказать, что все возможные расширения этой программы прекращаются, если только возможности расширения этой программы не являются полными по Тьюрингу (например, через зависимую типизацию). Поскольку основным требованием для полноты по Тьюрингу является неограниченная рекурсия , интуитивно понятно, как теоремы Гёделя о неполноте и парадокс Рассела применимы к расширению.
Интерпретация этих теорем включает их в обобщенное концептуальное понимание энтропийной силы:
источник
Я вижу прямоугольники и квадраты в каждом ответе, и как нарушать LSP.
Я хотел бы показать, как LSP может быть согласован с реальным примером:
Этот дизайн соответствует LSP, потому что поведение остается неизменным независимо от реализации, которую мы решили использовать.
И да, вы можете нарушить LSP в этой конфигурации, сделав одно простое изменение следующим образом:
Теперь подтипы нельзя использовать одинаково, поскольку они больше не дают того же результата.
источник
Database::selectQuery
поддержки только подмножества SQL, поддерживаемого всеми механизмами БД. Это вряд ли практично ... Тем не менее, пример все еще легче понять, чем большинство других, используемых здесь.Существует контрольный список, чтобы определить, нарушаете ли вы или нет Лискова.
Контрольный список:
Ограничение истории : при переопределении метода вам не разрешено изменять немодифицируемое свойство в базовом классе. Взгляните на этот код, и вы увидите, что Имя определено как немодифицируемое (закрытый набор), но SubType представляет новый метод, который позволяет модифицировать его (посредством отражения):
Есть еще 2 пункта: Контравариантность аргументов метода и Ковариантность возвращаемых типов . Но это не возможно в C # (я разработчик C #), поэтому мне плевать на них.
Ссылка:
источник
LSP - это правило о договоре предложений: если базовый класс удовлетворяет договору, то производные классы LSP также должны удовлетворять этому договору.
В псевдо-питоне
удовлетворяет LSP, если каждый раз, когда вы вызываете Foo для объекта Derived, он дает те же результаты, что и вызов Foo для объекта Base, при условии, что аргумент arg одинаков.
источник
2 + "2"
). Возможно, вы путаете «строго типизированный» со «статически типизированным»?Длинная короткая история, давайте оставить прямоугольники прямоугольники и квадраты квадратов, практический пример , когда расширение родительского класса, вы должны либо сохранить точный родительский API или удлинить его.
Допустим, у вас есть база ItemsRepository.
И подкласс, расширяющий это:
Тогда у вас мог бы быть Клиент, работающий с API Base ItemsRepository и опирающийся на него.
LSP нарушается , когда подставляя родительский класс с суб брейков класса контракта АНИ в .
Вы можете узнать больше о написании поддерживаемого программного обеспечения в моем курсе: https://www.udemy.com/enterprise-php/
источник
Когда я впервые прочитал о LSP, я предположил, что это подразумевалось в очень строгом смысле, по сути приравнивая его к реализации интерфейса и приведению типов к типу. Что означало бы, что LSP обеспечивается или не обеспечивается самим языком. Например, в этом строгом смысле ThreeDBoard, безусловно, заменяет Board в том, что касается компилятора.
После прочтения более подробно о концепции, я обнаружил, что LSP обычно интерпретируется более широко, чем это.
Короче говоря, то, что означает для клиентского кода «знать», что объект за указателем имеет производный тип, а не тип указателя, не ограничивается безопасностью типов. Приверженность LSP также может быть проверена путем исследования фактического поведения объектов. Таким образом, исследуя влияние состояния объекта и аргументов метода на результаты вызовов метода или типы исключений, выбрасываемых из объекта.
Возвращаясь к примеру снова, теоретически можно заставить методы Board работать на ThreeDBoard просто отлично. На практике, однако, будет очень трудно предотвратить различия в поведении, которые клиент может не обработать должным образом, не ограничивая функциональность, которую ThreeDBoard намеревается добавить.
Имея эти знания в руках, оценка соблюдения LSP может быть отличным инструментом для определения того, когда композиция является более подходящим механизмом для расширения существующей функциональности, а не наследования.
источник
Полагаю, что все понимали, что такое LSP технически: вы в основном хотите абстрагироваться от деталей подтипов и безопасно использовать супертипы.
Итак, у Лискова есть 3 базовых правила:
Правило подписи: должна быть правильная реализация каждой операции надтипа в подтипе синтаксически. Что-то, что компилятор сможет проверить для вас. Существует небольшое правило о том, что нужно генерировать меньше исключений и быть по крайней мере таким же доступным, как методы супертипа.
Правило методов: реализация этих операций семантически обоснована.
Правило свойств: это выходит за рамки отдельных вызовов функций.
Все эти свойства должны быть сохранены, и дополнительная функциональность подтипа не должна нарушать свойства супертипа.
Если об этих трех вещах заботятся, вы абстрагируетесь от базовых вещей и пишете слабосвязанный код.
Источник: Разработка программ на Java - Барбара Лисков
источник
Важным примером использования LSP является тестирование программного обеспечения .
Если у меня есть класс A, который является LSP-совместимым подклассом B, то я могу повторно использовать набор тестов B для тестирования A.
Чтобы полностью протестировать подкласс A, мне, вероятно, нужно добавить еще несколько тестовых случаев, но как минимум я могу повторно использовать все тестовые случаи суперкласса B.
Это можно понять, построив то, что Макгрегор называет «параллельной иерархией для тестирования»: мой
ATest
класс унаследован отBTest
. Затем требуется некоторая форма внедрения, чтобы тест-кейс работал с объектами типа A, а не типа B (подойдет простой шаблонный метод).Обратите внимание, что повторное использование набора супер-тестов для всех реализаций подкласса на самом деле является способом проверки того, что эти реализации подкласса совместимы с LSP. Таким образом, можно также утверждать, что следует запускать тестовый набор суперкласса в контексте любого подкласса.
См. Также ответ на вопрос Stackoverflow: « Могу ли я реализовать серию повторно используемых тестов для проверки реализации интерфейса? »
источник
Давайте проиллюстрируем на Java:
Здесь нет проблем, верно? Автомобиль определенно является транспортным устройством, и здесь мы видим, что он переопределяет метод startEngine () своего суперкласса.
Давайте добавим еще одно транспортное устройство:
Сейчас все идет не так, как планировалось! Да, велосипед является транспортным устройством, однако он не имеет двигателя и, следовательно, метод startEngine () не может быть реализован.
Решением этих проблем является правильная иерархия наследования, и в нашем случае мы решили бы эту проблему путем дифференциации классов транспортных устройств с двигателями и без них. Хотя велосипед - это транспортное средство, у него нет двигателя. В этом примере наше определение транспортного устройства неверно. У него не должно быть двигателя.
Мы можем реорганизовать наш класс TransportationDevice следующим образом:
Теперь мы можем расширить TransportationDevice для немоторизованных устройств.
И расширить транспортное устройство для моторизованных устройств. Здесь более уместно добавить объект Engine.
Таким образом, наш класс автомобилей становится более специализированным, придерживаясь принципа подстановки Лискова.
И наш велосипедный класс также соответствует принципу замещения Лискова.
источник
Эта формулировка LSP слишком сильна:
Что в основном означает, что S - это другая, полностью инкапсулированная реализация той же вещи, что и T. И я мог бы быть смелым и решить, что производительность является частью поведения P ...
Так что, в принципе, любое использование позднего связывания нарушает LSP. Весь смысл ОО в том, чтобы получить другое поведение, когда мы заменяем объект одного вида другим!
Формулировка, на которую ссылается Википедия , лучше, поскольку свойство зависит от контекста и не обязательно включает в себя все поведение программы.
источник
В очень простом предложении мы можем сказать:
Дочерний класс не должен нарушать характеристики своего базового класса. Он должен быть в состоянии с этим. Мы можем сказать, что это так же, как подтип.
источник
Пример:
Ниже приведен классический пример нарушения принципа подстановки Лискова. В примере используются 2 класса: Rectangle и Square. Давайте предположим, что объект Rectangle используется где-то в приложении. Расширяем приложение и добавляем класс Square. Класс square возвращается фабричным шаблоном, основанным на некоторых условиях, и мы не знаем точно, какой тип объекта будет возвращен. Но мы знаем, что это прямоугольник. Мы получаем объект прямоугольника, устанавливаем ширину 5 и высоту 10 и получаем площадь. Для прямоугольника с шириной 5 и высотой 10 площадь должна быть 50. Вместо этого результат будет 100
Смотрите также: принцип Open Close
Некоторые похожие концепции для лучшей структуры: Соглашение по конфигурации
источник
Принцип подстановки Лискова
источник
Некоторое дополнение:
я удивляюсь, почему никто не написал об Инварианте, предварительных условиях и пост-условиях базового класса, которым должны следовать производные классы. Чтобы производный класс D полностью соответствовал базовому классу B, класс D должен соответствовать определенным условиям:
Таким образом, производный должен знать о трех вышеупомянутых условиях, наложенных базовым классом. Следовательно, правила подтипирования заранее определены. Это означает, что отношения «IS A» должны соблюдаться только тогда, когда подтип подчиняется определенным правилам. Эти правила в форме инвариантов, предварительных условий и постусловий должны определяться формальным « контрактом на проектирование ».
Дальнейшие обсуждения по этому вопросу доступны в моем блоге: принцип замещения Лискова
источник
Проще говоря, LSP утверждает, что объекты одного и того же суперкласса должны иметь возможность обмениваться друг с другом, не нарушая ничего.
Например, если у нас есть
Cat
иDog
класс, производный отAnimal
класса, любые функции, использующие класс Animal, должны иметь возможность использоватьCat
илиDog
вести себя нормально.источник
Будет ли реализация ThreeDBoard с точки зрения массива Board настолько полезной?
Возможно, вы захотите рассматривать ломтики ThreeDBoard в различных плоскостях как доску. В этом случае вы можете захотеть абстрагировать интерфейс (или абстрактный класс) для Board, чтобы учесть несколько реализаций.
Что касается внешнего интерфейса, вы можете выделить интерфейс Board для TwoDBoard и ThreeDBoard (хотя ни один из вышеперечисленных методов не подходит).
источник
Квадрат - это прямоугольник, ширина которого равна высоте. Если квадрат устанавливает два разных размера для ширины и высоты, он нарушает инвариант квадрата. Это обходится путем введения побочных эффектов. Но если у прямоугольника есть setSize (высота, ширина) с предварительным условием 0 <высота и 0 <ширина. Метод производного подтипа требует height == width; более сильное предварительное условие (и это нарушает LSP). Это показывает, что хотя квадрат является прямоугольником, он не является допустимым подтипом, поскольку предварительное условие усиливается. Обход (вообще плохая вещь) вызывает побочный эффект, и это ослабляет почтовое условие (которое нарушает lsp). У setWidth на базе есть условие post 0 <width. Производная ослабляет его с высотой == ширина.
Поэтому квадрат с изменяемым размером не является прямоугольником с изменяемым размером.
источник
Этот принцип был введен Барбарой Лисков в 1987 году и расширяет принцип Open-Closed, сосредоточив внимание на поведении суперкласса и его подтипов.
Его важность становится очевидной, когда мы рассматриваем последствия его нарушения. Рассмотрим приложение, которое использует следующий класс.
Представьте, что однажды клиент требует умения манипулировать квадратами в дополнение к прямоугольникам. Поскольку квадрат является прямоугольником, класс квадрата должен быть производным от класса Rectangle.
Однако при этом мы столкнемся с двумя проблемами:
Квадрату не нужны переменные высоты и ширины, унаследованные от прямоугольника, и это может привести к значительным потерям памяти, если нам нужно будет создать сотни тысяч квадратных объектов. Свойства установщика ширины и высоты, унаследованные от прямоугольника, не подходят для квадрата, поскольку ширина и высота квадрата идентичны. Чтобы установить одинаковые значения для высоты и ширины, мы можем создать два новых свойства следующим образом:
Теперь, когда кто-то установит ширину квадратного объекта, его высота изменится соответственно и наоборот.
Давайте двигаться вперед и рассмотрим эту другую функцию:
Если мы передадим ссылку на квадратный объект в эту функцию, мы нарушим LSP, потому что функция не работает для производных своих аргументов. Свойства width и height не являются полиморфными, потому что они не объявлены виртуальными в прямоугольнике (квадратный объект будет поврежден, потому что высота не будет изменена).
Однако, объявив свойства сеттера виртуальными, мы столкнемся с другим нарушением, OCP. Фактически создание производного квадрата класса вызывает изменения в прямоугольнике базового класса.
источник
Самым ясным объяснением для LSP, которое я нашел до сих пор, было «Принцип подстановки Лискова говорит, что объект производного класса должен иметь возможность заменить объект базового класса без внесения каких-либо ошибок в систему или изменения поведения базового класса». "от сюда . В статье приведен пример кода для нарушения LSP и его исправления.
источник
Допустим, мы используем прямоугольник в нашем коде
В нашем классе геометрии мы узнали, что квадрат - это особый тип прямоугольника, потому что его ширина равна длине его высоты. Давайте также создадим
Square
класс на основе этой информации:Если мы заменим
Rectangle
сSquare
в нашем первом коде, то он сломается:Это происходит потому , что
Square
есть новое предварительное условие у нас не было вRectangle
классе:width == height
. Согласно LSPRectangle
экземпляры должны заменятьсяRectangle
экземплярами подкласса. Это связано с тем, что эти экземпляры проходят проверку типа дляRectangle
экземпляров, и поэтому они вызывают непредвиденные ошибки в вашем коде.Это был пример для части «предварительные условия не могут быть усилены в подтипе» в статье вики . Итак, подведем итог: нарушение LSP в какой-то момент может вызвать ошибки в вашем коде.
источник
LSP говорит, что «объекты должны быть заменены их подтипами». С другой стороны, этот принцип указывает на
и следующий пример помогает лучше понять LSP.
Без LSP:
Фиксация по LSP:
источник
Я рекомендую вам прочитать статью: Нарушение принципа подстановки Лискова (LSP) .
Там вы можете найти объяснение принципа подстановки Лискова, общие подсказки, которые помогут вам угадать, если вы уже нарушили его, и пример подхода, который поможет вам сделать вашу иерархию классов более безопасной.
источник
ПРИНЦИП ЗАМЕНЫ ЛИСКОВ (Из книги Марка Симанна) утверждает, что мы должны иметь возможность заменить одну реализацию интерфейса на другую, не нарушая ни клиента, ни реализацию. Это тот принцип, который позволяет удовлетворять требования, возникающие в будущем, даже если мы можем ' не предвидеть их сегодня.
Если мы отсоединяем компьютер от стены (Внедрение), ни настенная розетка (Интерфейс), ни компьютер (Клиент) не выходят из строя (фактически, если это портативный компьютер, он может даже работать от батарей в течение определенного периода времени) , Однако с программным обеспечением клиент часто ожидает, что услуга будет доступна. Если служба была удалена, мы получаем исключение NullReferenceException. Чтобы справиться с ситуацией такого типа, мы можем создать реализацию интерфейса, который «ничего не делает». Это шаблон проектирования, известный как Null Object, [4], и он примерно соответствует отключению компьютера от стены. Поскольку мы используем слабую связь, мы можем заменить реальную реализацию чем-то, что ничего не делает, не вызывая проблем.
источник
Принцип подстановки Ликова гласит, что если программный модуль использует базовый класс, то ссылку на базовый класс можно заменить производным классом, не влияя на функциональность программного модуля.
Намерение - Производные типы должны полностью заменить свои базовые типы.
Пример - ко-вариантные типы возврата в Java.
источник
Вот выдержка из этого поста, в которой все проясняется:
[..] Чтобы понять некоторые принципы, важно понимать, когда они были нарушены. Это то, что я буду делать сейчас.
Что означает нарушение этого принципа? Это подразумевает, что объект не выполняет контракт, наложенный абстракцией, выраженной интерфейсом. Другими словами, это означает, что вы неправильно определили свои абстракции.
Рассмотрим следующий пример:
Это нарушение LSP? Да. Это связано с тем, что договор об аккаунте говорит нам, что аккаунт будет отозван, но это не всегда так. Итак, что я должен сделать, чтобы это исправить? Я просто изменяю договор:
Вуаля, сейчас контракт выполнен.
Это тонкое нарушение часто навязывает клиенту способность различать конкретные используемые объекты. Например, учитывая контракт первого Аккаунта, он может выглядеть следующим образом:
И это автоматически нарушает принцип «открыто-закрыто» [то есть требование снятия денег. Потому что вы никогда не знаете, что происходит, если у объекта, нарушающего договор, не хватает денег. Возможно, он просто ничего не возвращает, возможно, будет выдано исключение. Таким образом, вы должны проверить, если это
hasEnoughMoney()
- что не является частью интерфейса. Так что эта принудительная проверка, зависящая от конкретного класса, является нарушением OCP].Этот пункт также обращается к неправильному представлению, с которым я часто сталкиваюсь по поводу нарушения LSP. В нем говорится «если поведение ребенка изменилось у ребенка, то это нарушает LSP». Тем не менее, это не так - до тех пор, пока ребенок не нарушает родительский договор.
источник