Czy rozumiem poprawnie, że nie można przestrzegać zasady substytucji Liskowa w językach, w których obiekty mogą się sprawdzać, tak jak w przypadku języków pisanych kaczorkiem?
Na przykład, w Ruby, jeśli klasa B
dziedziczy z klasy A
, a następnie za każdy przedmiot x
z A
, x.class
jedzie do powrotu A
, ale jeśli x
jest przedmiotem B
, x.class
nie ma powrotu A
.
Oto oświadczenie LSP:
Niech q (x) być właściwością dowodliwe o obiekty x typu T . Następnie Q (y) powinny być udowodnione dla obiektów Y typu S , gdzie S jest podtypem T .
Na przykład w Ruby
class T; end
class S < T; end
naruszają LSP w tej formie, o czym świadczy właściwość q (x) =x.class.name == 'T'
Dodanie. Jeśli odpowiedź brzmi „tak” (LSP niezgodny z introspekcją), moje inne pytanie brzmiałoby: czy istnieje jakaś zmodyfikowana „słaba” forma LSP, która może być w stanie utrzymać dynamiczny język, być może pod pewnymi dodatkowymi warunkami i tylko ze specjalnymi typami o właściwościach .
Aktualizacja. Dla porównania, oto inne sformułowanie LSP, które znalazłem w sieci:
Funkcje korzystające ze wskaźników lub referencji do klas podstawowych muszą mieć możliwość korzystania z obiektów klas pochodnych bez ich znajomości.
I kolejny:
Jeśli S jest zadeklarowanym podtypem T, obiekty typu S powinny zachowywać się tak, jak obiekty typu T powinny się zachowywać, jeśli są traktowane jako obiekty typu T.
Ostatni jest opatrzony adnotacjami:
Zauważ, że LSP dotyczy oczekiwanego zachowania obiektów. Można śledzić LSP tylko wtedy, gdy jest jasne, jakie jest oczekiwane zachowanie obiektów.
Wydaje się, że jest to słabsze niż pierwotne i może być możliwe do zaobserwowania, ale chciałbym, aby było to sformalizowane, w szczególności wyjaśnione, kto decyduje o oczekiwanym zachowaniu.
Czy zatem LSP nie jest własnością pary klas w języku programowania, ale pary klas wraz z danym zestawem właściwości, spełnianych przez klasę przodka? Czy w praktyce oznacza to, że aby skonstruować podklasę (klasę potomną) w odniesieniu do LSP, wszystkie możliwe zastosowania klasy przodka muszą być znane? Według LSP klasa przodków ma być zastępowalna dowolną klasą potomną, prawda?
Aktualizacja. Przyjąłem już odpowiedź, ale chciałbym dodać jeszcze jeden konkretny przykład od Ruby, aby zilustrować pytanie. W Ruby każda klasa jest modułem w tym sensie, że Class
klasa jest potomkiem Module
klasy. Jednak:
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)