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)
Odpowiedzi:
Oto faktyczna zasada :
I doskonałe streszczenie Wikipedii :
I kilka istotnych cytatów z artykułu:
Przejdźmy do pytania:
Nie.
A.class
zwraca klasę.B.class
zwraca klasę.Ponieważ możesz wykonać to samo połączenie na bardziej konkretnym typie i uzyskać zgodny wynik, LSP wstrzymuje. Problem polega na tym, że w przypadku dynamicznych języków nadal można wywoływać różne wyniki, oczekując, że będą dostępne.
Ale zastanówmy się nad statycznym, strukturalnym językiem (kaczym). W takim przypadku
A.class
zwróci typ z ograniczeniem, które musi być,A
lub podtypemA
. Daje to statyczną gwarancję, że każdy podtypA
musi zapewniać metodę,T.class
której wynikiem jest typ spełniający to ograniczenie.Zapewnia to silniejsze twierdzenie, że LSP utrzymuje się w językach, które obsługują pisanie kaczek, i że każde naruszenie LSP w czymś takim jak Ruby występuje częściej z powodu normalnego niewłaściwego użycia dynamicznego niż niezgodności projektu językowego.
źródło
fail unless x.foo == 42
i podtyp zwraca 0, to to samo. To nie jest awaria LSP, to normalne działanie twojego programu. Polimorfizm nie stanowi naruszenia LSP.W kontekście LSP „właściwość” to coś, co można zaobserwować na typie (lub obiekcie). W szczególności mówi się o „własności do udowodnienia”.
Taka „właściwość” mogłaby istnieć w przypadku
foo()
metody, która nie ma wartości zwrotnej (i jest zgodna z umową określoną w jej dokumentacji).Upewnij się, że nie pomylisz tego terminu z „właściwość”, ponieważ „
class
jest własnością każdego obiektu w Rubim”. Taka „właściwość” może być „właściwością LSP”, ale nie jest automatycznie taka sama!Teraz odpowiedź na twoje pytania zależy w dużej mierze od tego, jak surowo zdefiniujesz „właściwość”. Jeśli powiesz „własność klasy
A
jest to, że.class
powróci typ obiektu”, wtedyB
rzeczywiście nie mają tę właściwość.Jeśli jednak zdefiniować „własność” być „
.class
powracaA
”, to oczywiścieB
nie nie posiadają tę właściwość.Jednak druga definicja nie jest zbyt przydatna, ponieważ zasadniczo znalazłeś okrągły sposób na zadeklarowanie stałej.
źródło
.class
zwróci typ obiektu”. Jeśli to oznaczax.class == x.class
, nie jest to interesująca właściwość.Jak rozumiem, w introspekcji nie ma nic, co byłoby niezgodne z LSP. Zasadniczo, o ile obiekt obsługuje te same metody co inne, oba powinny być wymienne. Oznacza to, że jeśli kod oczekuje
Address
obiekt, to nie ma znaczenia, czy jest toCustomerAddress
lubWarehouseAddress
, o ile oba dostarczają (na przykład)getStreetAddress()
,getCityName()
,getRegion()
igetPostalCode()
. Z pewnością możesz stworzyć dekorator, który pobiera inny typ obiektu i wykorzystuje introspekcję, aby zapewnić wymagane metody (np.DestinationAddress
Klasa, która przyjmujeShipment
obiekt i przedstawia adres dostawy jakoAddress
), ale nie jest to wymagane i na pewno nie zapobiegają stosowaniu LSP.źródło
x.class.name
z'A'
, co skuteczniex.class.name
bezużyteczny .x.class.name == 'A'
jest to anty-wzór w pisaniu kaczek: w końcu pisanie kaczki pochodzi od „Jeśli drży i chodzi jak kaczka, to jest kaczka”. Jeśli więc zachowuje sięA
i honoruje przedstawione umowyA
, to jest toA
przypadekPo przejrzeniu oryginalnej pracy Barbary Liskov odkryłem, jak uzupełnić definicję Wikipedii, aby LSP rzeczywiście był zadowolony w prawie każdym języku.
Przede wszystkim słowo „możliwe do udowodnienia” jest ważne w definicji. Nie jest to wyjaśnione w artykule na Wikipedii, a „ograniczenia” są wymienione gdzie indziej, bez odniesienia do niego.
Oto pierwszy ważny cytat z artykułu:
A oto druga, wyjaśniająca, czym jest specyfikacja typu :
Zatem LSP ma sens tylko w odniesieniu do danych specyfikacji danego typu , a dla odpowiedniej specyfikacji typu (na przykład dla pustej) może być spełniony prawdopodobnie w dowolnym języku.
Uważam, że odpowiedź Telastyn jest najbliższa temu, czego szukałem, ponieważ „ograniczenia” zostały wyraźnie wymienione.
źródło
x.class.name = 'A'
można udowodnić dla wszystkichx
klas,A
jeśli pozwolisz na zbyt dużą wiedzę. Specyfikacja typu nie została zdefiniowana, a jej dokładny związek z LSP również nie był określony, chociaż nieoficjalnie podano pewne wskazówki. Znalazłem już to, czego szukałem w pracy Liskova i odpowiedziałem na moje pytanie powyżej.x
z tego typux.woozle
daundefined
, to żaden typ, dla któregox.woozle
nie daje, nieundefined
będzie właściwym podtypem. Jeśli nadtyp nie udokumentuje niczegox.woozle
, fakt, że użyciex.woozle
nadtypu przydarzy sięundefined
, nie dałoby nic do zrozumienia, co mogłoby zrobić na podtypie.Cytując artykuł Wikipedii na temat LSP , „podstawialność jest zasadą w programowaniu obiektowym”. Jest to zasada i część projektu twojego programu. Jeśli piszesz kod, który zależy
x.class == A
, to twój kod narusza LSP. Uwaga: ten rodzaj zepsutego kodu jest również możliwy w Javie, nie trzeba wpisywać kaczki.Nic w wpisywaniu kaczek z natury nie łamie LSP. Tylko jeśli użyjesz go niewłaściwie, jak w twoim przykładzie.
Dodatkowa myśl: czy jawne sprawdzanie, czy klasa obiektu nie spełnia celu pisania kaczką?
źródło
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).
. Dokładna klasa obiektu NIE jest jedną z „pożądanych właściwości programu”; w przeciwnym razie byłoby to sprzeczne nie tylko z pisaniem kaczek, ale ogólnie z podtytułem, włącznie ze smakiem Javy.x.class == A
narusza zarówno LSP, jak i pisanie na klawiaturze. Nie ma sensu używać pisania kaczego, jeśli chcesz sprawdzić rzeczywiste typy.A
,B
) spełnia LSP, albo nie. Jeśli LSP zależy od kodu używanego w innym miejscu, nie jest wyjaśnione, jaki kod jest dozwolony. Mam nadzieję znaleźć coś tutaj: 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
. Oczywiste jest, żex.class
nie jest to jedna z interesujących właściwości; w przeciwnym razie polimorfizm Javy też nie działałby. W „problemie x.class” nie ma nic nieodłącznego od pisania kaczką. Czy zgadzasz się do tej pory?