Правильно ли я понимаю, что принцип подстановки Лискова не может соблюдаться в языках, где объекты могут сами себя проверять, как, например, в языках с утиной типизацией?
Например, в Ruby, если класс B
наследует от класса A
, то для каждого объекта x
из A
, x.class
собирается вернуться A
, но если x
это объект B
, x.class
не собирается возвращаться A
.
Вот заявление LSP:
Пусть д (х) является свойством доказуемо об объектах х типа Т . Тогда д (у) должно быть доказуемо для объектов , у типа S , где S является подтипом T .
Так в Ruby, например,
class T; end
class S < T; end
нарушать LSP в этой форме, о чем свидетельствует свойство q (x) =x.class.name == 'T'
Дополнение. Если ответ «да» (LSP несовместим с интроспекцией), тогда мой другой вопрос: есть ли какая-то измененная «слабая» форма LSP, которая может быть применима для динамического языка, возможно, при некоторых дополнительных условиях и только с особыми типами из свойств .
Обновить. Для справки, вот еще одна формулировка LSP, которую я нашел в Интернете:
Функции, которые используют указатели или ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.
И другой:
Если S является объявленным подтипом T, объекты типа S должны вести себя так же, как и объекты типа T, если они обрабатываются как объекты типа T.
Последний отмечен:
Обратите внимание, что LSP - это все об ожидаемом поведении объектов. Следовать LSP можно, только если ясно, каково ожидаемое поведение объектов.
Кажется, что это слабее, чем первоначальный, и его можно было бы наблюдать, но я бы хотел, чтобы это было формализовано, в частности объяснил, кто решает, каково ожидаемое поведение.
Тогда является ли LSP свойством не пары классов в языке программирования, а пары классов вместе с заданным набором свойств, удовлетворяемых классом предка? Практически, означает ли это, что для создания подкласса (класса-потомка), уважающего LSP, должны быть известны все возможные варианты использования класса-предка? Согласно LSP, класс предков должен быть заменен любым классом-потомком, верно?
Обновить. Я уже принял ответ, но я хотел бы добавить еще один конкретный пример из Ruby, чтобы проиллюстрировать вопрос. В Ruby каждый класс является модулем в том смысле, что Class
класс является потомком Module
класса. Однако:
class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module
module M; end
M.class # => Module
o = Object.new
o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)
Ответы:
Вот фактический принцип :
И отличное резюме Википедии :
И некоторые соответствующие цитаты из бумаги:
Итак, к вопросу:
Нет.
A.class
возвращает класс.B.class
возвращает класс.Поскольку вы можете сделать один и тот же вызов для более конкретного типа и получить совместимый результат, LSP удерживает. Проблема в том, что с динамическими языками вы все равно можете называть вещи по результату, ожидая, что они будут там.
Но давайте рассмотрим статически, структурный (утиный) типизированный язык. В этом случае
A.class
будет возвращен тип с ограничением, которым он должен быть,A
или подтипомA
. Это обеспечивает статическую гарантию того, что любой подтипA
должен предоставлять методT.class
, результатом которого является тип, удовлетворяющий этому ограничению.Это обеспечивает более сильное утверждение о том, что LSP поддерживается в языках, поддерживающих типизацию утиной утилитой, и что любое нарушение LSP в чем-то вроде Ruby происходит в большей степени из-за обычного динамического неправильного использования, чем из-за несовместимости языкового дизайна.
источник
fail unless x.foo == 42
и подтип возвращает 0, это то же самое. Это не сбой LSP, это нормальная работа вашей программы. Полиморфизм не является нарушением LSP.В контексте LSP «свойство» - это то, что можно наблюдать для типа (или объекта). В частности, речь идет о «доказуемой собственности».
Такое «свойство» может существовать
foo()
методом, который не имеет возвращаемого значения (и следует контракту, установленному в его документации).Убедитесь, что вы не путаете этот термин с «свойством», так как «
class
свойство каждого объекта в Ruby». Такое «свойство» может быть «свойством LSP», но это не то же самое автоматически!Теперь ответ на ваши вопросы во многом зависит от того, насколько строгое вы определяете «свойство». Если вы говорите , «свойство класса
A
является то , что.class
возвращает тип объекта», то наB
самом деле делает имеет это свойство.Если, однако, вы определяете «свойство» быть «
.class
возвращаетсяA
», то очевидно , чтоB
это не имеет это свойство.Однако второе определение не очень полезно, поскольку вы, по сути, нашли обходной способ объявления константы.
источник
.class
вернет тип объекта». Если это означает, чтоx.class == x.class
это не интересная собственность.Насколько я понимаю, в самоанализе нет ничего, что было бы несовместимо с LSP. По сути, пока объект поддерживает те же методы, что и другой, оба должны быть взаимозаменяемыми. То есть, если ваш код ожидает
Address
объект, то это не имеет значения , если этоCustomerAddress
илиWarehouseAddress
, до тех пор, как обеспечить (например)getStreetAddress()
,getCityName()
,getRegion()
иgetPostalCode()
. Вы, конечно, можете создать какой-то тип декоратора, который принимает объект другого типа и использует интроспекцию для предоставления требуемых методов (например,DestinationAddress
класс, который принимаетShipment
объект и представляет адрес доставки какAddress
), но это не обязательно и, безусловно, не не препятствует применению LSP.источник
x.class.name
с'A'
, эффективно делаяx.class.name
бесполезным .x.class.name == 'A'
является ли анти-шаблон в типизации утки: в конце концов, типизация утки происходит от "Если она крякает и ходит как утка, это утка". Так что, если он ведет себя какA
иA
A
Просматривая оригинальную статью Барбары Лисков, я нашел, как завершить определение Википедии, чтобы LSP действительно мог быть удовлетворен практически на любом языке.
Прежде всего, слово «доказуемое» важно в определении. Это не объясняется в статье в Википедии, и «ограничения» упоминаются в других местах без ссылки на них.
Вот первая важная цитата из статьи:
И вот второй, объясняющий, что такое спецификация типа :
Таким образом, LSP имеет смысл только в отношении заданных спецификаций типов. , и для соответствующей спецификации типа (например, для пустой) она может быть выполнена на любом языке.
Я считаю, что ответ Теластина наиболее близок к тому, что я искал, потому что «ограничения» были упомянуты явно.
источник
x.class.name = 'A'
доказуемо для всегоx
класса,A
если вы позволите слишком много знаний. Спецификация типа не была определена, и ее точное отношение к LSP также не было, хотя неофициально были даны некоторые указания. Я уже нашел то, что искал в статье Лискова и ответил на мой вопрос выше.x
из этого типа,x.woozle
будет даватьundefined
, то ни один тип, для которогоx.woozle
не дает,undefined
будет правильным подтипом. Если супертип ничего не документируетx.woozle
, тот факт, что использованиеx.woozle
супертипа приведет к выходуundefined
, ничего не будет означать, что может повлиять на подтип.Цитируя статью Википедии о LSP , «заменяемость является принципом объектно-ориентированного программирования». Это принцип и часть дизайна вашей программы. Если вы пишете код, который зависит от
x.class == A
, то это ваш код нарушает LSP. Обратите внимание, что этот тип испорченного кода также возможен в Java, нет необходимости вводить утку.Ничто в наборе утки по своей сути не нарушает LSP. Только если вы его неправильно используете, как в вашем примере.
Дополнительная мысль: неужели проверка в явном виде на предметный класс не побеждает цель утиной типизации?
источник
Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
. Точный класс объекта НЕ является одним из «желательных свойств программы»; в противном случае это противоречило бы не только типизации уток, но и подтипам в целом, включая Java-вкус.x.class == A
нарушает как LSP, так и утку. Нет смысла использовать утку, если вы собираетесь проверять фактические типы.A
,B
) удовлетворяют LSP, либо нет. Если LSP зависит от кода, используемого в другом месте, то не объясняется, какой код разрешен. Я надеюсь найти что-то здесь: cse.ohio-state.edu/~neelam/courses/788/lwb.pdfLet q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T
. Очевидно, чтоx.class
это не одно из интересных свойств здесь; иначе полиморфизм Java не сработает. В вашей "проблеме x.class" нет ничего, что присуще утке. Вы согласны до сих пор?