Правильно ли я понимаю, что принцип подстановки Лискова не может соблюдаться в языках, где объекты могут сами себя проверять, как, например, в языках с утиной типизацией?
Например, в 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)