Не могут ли ослабление предусловий и усиление постусловий также нарушить принцип замещения Лискова?

Фактическое предусловие подтипа создается путем комбинирования (с использованием логических OR) предварительных условий базового типа и предварительных условий подтипа, в результате чего < em> результирующее предварительное условие менее ограничительное

Фактическое постусловие подтипа создается путем объединения (с использованием логических AND) постусловий базового типа и постусловий подтипа, в результате чего < em> результирующее постусловие более ограничительное

Ниже приведены примеры усиления предусловий и ослабления постусловий, которые в результате нарушают LSP (Ссылка):

  1. Предположим, ваш базовый класс работает с членом int. Теперь ваш подтип требует, чтобы int был положительным. Это усиленные предварительные условия, и теперь любой код, который раньше отлично работал с отрицательными целыми числами, сломан.

  2. Точно так же предположим тот же сценарий, но базовый класс, используемый для гарантии того, что член будет положительным после вызова. Затем подтип изменяет поведение, чтобы разрешить отрицательные целые числа. Код, который работает с объектом (и предполагает, что пост-условие - положительное целое число), теперь нарушен, поскольку пост-условие не поддерживается.

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

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

Пример:

Постусловие базового класса гарантирует, что возвращаемое значение метода будет в пределах диапазона 1-10, но тогда подтип изменяет постусловие , чтобы возвращаемое значение находилось только в диапазоне 2-9. Теперь код, который работает с объектом, возвращаемым этим методом (и предполагает, что постусловие находится в диапазоне 1-10), нарушен, поскольку постусловие не поддерживается.


person EdvRusj    schedule 08.05.2013    source источник


Ответы (5)


Извините, но в ваших соображениях есть логическая ошибка.

Постусловие базового класса гарантирует, что возвращаемое значение метода будет в диапазоне 1-10, но затем подтип изменяет постусловие, чтобы разрешить возвращаемое значение только в диапазоне 2-9.

Поскольку код работает в диапазоне 1–10, а диапазон 2–9 фактически находится в диапазоне 1–10, усиление постусловия никогда не должно вызывать проблем.

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

person mrAtari    schedule 22.09.2015

Думаю, ваш пример не поддерживает вашу точку зрения в следующем смысле.

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

Правило постусловия на самом деле гласит:

Это правило гласит, что метод подтипа предоставляет больше, чем метод супертипа: когда он возвращает все, что предоставляет метод супертипа, гарантировано, а также, возможно, некоторые дополнительные эффекты (Разработка программ на Java, стр. 177)

Ваш пример не гарантирует всего, что гарантирует супертип. Я предполагаю, что в вашем примере усиление будет означать, что подтип гарантирует, что возвращаемый int находится в диапазоне 1-10, кроме того, он гарантирует, что возвращаемый int находится между 2-9, а не только вторым.

person eytanfb    schedule 13.06.2013

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

Хотя может показаться неэлегантным иметь условия, при которых поведение класса не определено, добавление функций к классу при сохранении восходящей совместимости обычно требует изменения того, что было неопределенным поведением, на заданное. В некоторых случаях «неуказанным» поведением класса может быть «сбой компиляции», но нет гарантии, что код, пытающийся использовать такой член, всегда не сможет скомпилировать. Например, если в контракте класса не упоминается член «Foo», попытка использовать такой член, скорее всего, вызовет ошибку компиляции, но будущая версия класса может определять Foo без нарушения его контракта.

person supercat    schedule 29.07.2013

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

(Нарушение контракта - это неизвестное поведение, просто так случается, что наиболее распространенный подход - выбросить исключение.)

Что касается усиления постусловия, я не вижу вашей точки зрения, правда (?) Если в контракте указаны значения 1-10, любые значения между 1-10 де-факто входят в спецификацию, также 2-9 или даже 3 всегда ( ?)

person keyoxy    schedule 03.12.2014

Вы совершенно правы. Предпосылки нельзя ослаблять. Это также изменило бы поведение базового типа. Например:

class Base { void method(int x) { /* x: 1-100 allowed else exception  */ } }
class Weak: Base { void method(int x) { /* x: 1-1000  allowed else exception  */ } }
class Strong: Base { void method(int x) { /* x: 1-10  allowed else exception  */ } }

int Main() {
  Base base = new Base();
  base.method(101-1000); // exception

  Base base2 = new Weak();
  base2.method(101-1000); // ok

  Base base3 = new Strong();
  base3.method(101-1000); // exception 
}

LSP явно нарушен: слабый класс для 101-1000 нормально, но базовый класс для 101-1000 выдает исключение. Это явно не то же самое поведение.

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

То же самое и с постусловиями, наоборот.

person hce    schedule 08.10.2014
comment
Нет смысла сравнивать поведение базового класса и подкласса, если вы в первую очередь нарушаете контракт (1-100) базового класса. LSP применяется только в пределах спецификации базового класса. Вне спецификации - неизвестное поведение. - person keyoxy; 04.12.2014