Hejo
Mam dlemacik, ostatnio się zastanawiałem nad Interfejsem List i nad operacjami mutowalnymi.
Jeżeli do interfejsu list przypiszemy liste niemutowalną to oczekiwanym zachowaniem metody add będzie rzucenie wyjątku.
No i pytanie czy to łamie zasade liskov? Z jednej strony dokumentacja mówi że metoda add może rzucić wyjątkiem i jest to oczekiwany efekt.
Ale od razu nasuwa mi się pytanie, to po co jest metoda która może rzucać taki wyjątek. Jeżeli ktoś operuje na liście, to co ma zawsze się zabezpieczać przed tym że ktoś może wstawić niemutowalną kolekcje (wiadomo modyfikowanie listy jest bee, ale załóżmy taki przypadek).
- 1
- 2

- Rejestracja:prawie 7 lat
- Ostatnio:ponad 2 lata
- Lokalizacja:74.7261832, -41.7409518
- Postów:151

- Rejestracja:ponad 8 lat
- Ostatnio:około godziny
Oczywiscie ze to lamie Liskov.
Po prostu porzuc java.util i przerzuc sie na Vavr






- Rejestracja:prawie 9 lat
- Ostatnio:5 miesięcy
- Lokalizacja:Futurama
- Postów:887
Zasada:
Funkcje które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów.
Definicja w dokumentacji:
boolean add(E e)
Appends the specified element to the end of this list (optional operation).
Lists that support this operation may place limitations on what elements may be added to this list. In particular, some lists will refuse to add null elements, and others will impose restrictions on the type of elements that may be added. List classes should clearly specify in their documentation any restrictions on what elements may be added.
Specified by:
add in interface Collection<E>
Parameters:
e - element to be appended to this list
Returns:
true (as specified by Collection.add(E))
Throws:
UnsupportedOperationException - if the add operation is not supported by this list
ClassCastException - if the class of the specified element prevents it from being added to this list
NullPointerException - if the specified element is null and this list does not permit null elements
IllegalArgumentException - if some property of this element prevents it from being added to this list
No i teraz tak - dokumentacja mówi Ci wprost co powinieneś obsłużyć, żeby mieć pewność, że każdy podtyp będzie elegancko obsłużony. @stivens mówi, że oczywiście łamie Liskov. Ja się nie zgadzam. Dostajesz wprost info co powinieneś zrobić, żeby wszystko śmigało. Nawet masz napisane, że dodawanie jest opcjonalne, tj. istnieje podzbiór podtypów, który nie będzie tego obsługiwał - stąd te wyjątki. To, że nie jest to tak oczywiste jakbyśmy chcieli nie powoduje, że zasada jest złamana. Tutaj bardziej łamiemy KISS, bo nie jest to intuicyjne.
Jeśli Ci to nie pasuje to powinieneś wybrać inny typ jako bazowy, tak aby samo sprawdzanie typów załatwiło Ci zgodność z akcjami, z których chcesz korzystać (o ile taki istnieje). Przerzucanie się na vavr jest jakąś opcją, to jest przyzwoita libka (choć sam nie miałem okazji stosować).
A i jeszcze jedna kwestia mówienie, że papier przyjmie wszystko moim zdaniem trochę nie do końca ma tutaj podstawy - tworzenie biblioteki standardowej nie jest łatwym zadaniem, zwłaszcza kiedy dbasz o kompatybilność wsteczną. Być może można to było zrobić lepiej (patrz -> Scala), być może nie, nie czuję się tutaj w żadnym wypadku upoważniony do oceny na podstawie tego co wyczytałem w necie. Wracając. nie bycie zgodnym z dokumentacją i narzekanie, że coś nie działa to trochę jak składanie mebli z Ikei bez instrukcji i denerwowanie się, że stół jest krzywy i czasami nam kawa spada

- Rejestracja:ponad 8 lat
- Ostatnio:około godziny
@Burdzi0: ten interfejs jest po prostu zaprojektowany przeciwko LSP i zadna dokumentacja tego nie zmieni.
Juz widze jak programisci obsluguja wyjatki przy List::add. Raczej nobody cares
.
Gdyby List bylo niemutowalne a potem bylo jakies MutableList extends List
to wszystko by bylo cacy.

- Rejestracja:prawie 9 lat
- Ostatnio:5 miesięcy
- Lokalizacja:Futurama
- Postów:887
@Burdzi0: ten interfejs jest po prostu zaprojektowany przeciwko LSP i zadna dokumentacja tego nie zmieni.
@stivens: udowodnij, że tak jest, bo jak na razie to tylko Twoja opinia xd
Podałem swoje argumenty i podparłem je oficjalną dokumentacją. Albo ja jestem w błędzie, albo Ty, dobrze by było wiedzieć xd
Juz widze jak programisci obsluguja wyjatki przy List::add. Raczej nobody cares.
Płakanie potem, że coś wybuchło jest tylko i wyłącznie ich winą.
Gdyby List bylo niemutowalne a potem bylo jakies MutableList extends List to wszystko by bylo cacy.
Zrobienie tego w obecnej bibliotece standardowej jest niemożliwe, jeśli chcesz mieć jakąkolwiek kompatybilność wsteczną.





- Rejestracja:ponad 8 lat
- Ostatnio:około godziny
Kazdy Pies jest Ssakiem ale nie kazdy Ssak jest Psem.
Kazda MutowalnaLista jest Lista ale nie kazda Lista jest MutowalnaLista.
Takie dziedziczenie jak proponuje biblioteka standardowa nie ma sensu
.






- Rejestracja:prawie 7 lat
- Ostatnio:dzień
- Lokalizacja:Kraków
- Postów:1999
Burdzi0 napisał(a):
No i teraz tak - dokumentacja mówi Ci wprost co powinieneś obsłużyć, żeby mieć pewność, że każdy podtyp będzie elegancko obsłużony. @stivens mówi, że oczywiście łamie Liskov. Ja się nie zgadzam. Dostajesz wprost info co powinieneś zrobić, żeby wszystko śmigało. Nawet masz napisane, że dodawanie jest opcjonalne, tj. istnieje podzbiór podtypów, który nie będzie tego obsługiwał - stąd te wyjątki. To, że nie jest to tak oczywiste jakbyśmy chcieli nie powoduje, że zasada jest złamana. Tutaj bardziej łamiemy KISS, bo nie jest to intuicyjne.
Jeśli Ci to nie pasuje to powinieneś wybrać inny typ jako bazowy, tak aby samo sprawdzanie typów załatwiło Ci zgodność z akcjami, z których chcesz korzystać (o ile taki istnieje). Przerzucanie się na vavr jest jakąś opcją, to jest przyzwoita libka (choć sam nie miałem okazji stosować).
No ja się nie zgodzę z Tobą, a zgodzę się ze @stivens - to, że ktoś sobie napisze w dokumentacji, albo w komentarzu w kodzie, albo na swoim blogu (będąc Chief Executive Guru Maintainerem czegoś co ma 150k gwiazdek na GH), że to jest big ball of mud by design
nie sprawi, że to przestanie być kulą błota i przestanie łamać LSP.
Jak ja lub Ty popełnię coś takiego w swoim kodzie i będę próbował wcisnąć komuś interfejs z paroma implementacjami mówiąc
no, wiesz, wprawdzie
AbstractDog
ma metodębark()
, ale używaj jej tylko naLivingDog
boDogStatue
iDeceasedDog
rzucają wyjątkiem
To to cóś wyląduje na śmietniku historii szybciej, niż zdążysz wymówić vavr
.
Jeśli jedna z implementacji wali beztrosko wyjątkiem, gdy spróbujesz wywołać na niej metodę z interfejsu lub klasy abstrakcyjnej, to z punktu widzenia LSP to jest po prostu ultra-bajzel i zdecydowanie nie powinno tak być. Nawet jeśli dopiszesz w javadocu tego interfejsiku, że rzucanie wyjątku jest by design.
Żeby to w ogóle miało szansę mieć ręce i nogi, to coś takiego jak MutableList powinno rozszerzać podstawowy interfejs List o mutowalność (prawdopodobnie implementując dodatkowy interfejs który wprowadza mutujące
metody). Ewentualnie, mogłyby mieć wspólny interfejs public List<T> add(T element)
, gdzie MutableList
dodawałby element do wewnętrznego stanu zwracając siebie, a ImmutableList
zwracałby nową, zapewne również niemutowalną listę - przynajmniej nie rozwalasz zachowania w jednej z implementacji, choć wtedy jedna metoda ma side-effects a druga nie, co też jest słabawe.

- Rejestracja:około 21 lat
- Ostatnio:prawie 3 lata
- Lokalizacja:Space: the final frontier
- Postów:26433
Ja osobiście uważam ze to raczej złamanie ISP -> interfejs jest zbyt bogaty i stąd implementacje które sobie z nim nie radzą ;)


if and only if
złamiesz ISP. Choć w tym przypadku tak chyba jest, że najpierw w interfejsie List
pojawiło się add
bo tak, a potem pojawiła się niemutowalna lista i nagle hmmm no tutaj prawda implementacje mają prawda prawo rzucać wyjątek, to jest by design


- Rejestracja:około 17 lat
- Ostatnio:3 dni
- Postów:1874
Hejo
Mam dlemacik, ostatnio się zastanawiałem nad Interfejsem List i nad operacjami mutowalnymi.
Jeżeli do interfejsu list przypiszemy liste niemutowalną to oczekiwanym zachowaniem metody add będzie rzucenie wyjątku.
No i pytanie czy to łamie zasade liskov? Z jednej strony dokumentacja mówi że metoda add może rzucić wyjątkiem i jest to oczekiwany efekt.
Łamie, ponieważ wg. zasady Liskov kod kliencki powinien działać poprawnie dla podtypów, a ten się po prostu wywali. Taką pułapką jest np. Arrays.asList(...)
, które zwraca niemodyfikowalną listę - nieraz się na to naciąłem.
Ale od razu nasuwa mi się pytanie, to po co jest metoda która może rzucać taki wyjątek. Jeżeli ktoś operuje na liście, to co ma zawsze się zabezpieczać przed tym że ktoś może wstawić niemutowalną kolekcje (wiadomo modyfikowanie listy jest bee, ale załóżmy taki przypadek).
Tak jest niestety w Javie, ale to nie jest argument, że to jest dobre podejście. Są na to 3 niewykluczające się wyjścia:
- piszesz testy, dzięki czemu masz wyspecyfikowane w jaki sposób kod jest używany (to zawsze)
- używasz lepszych kolekcji, które chronią nie w runtime, ale podczas kompilacji, np. tych z Vavra
- wszędzie robisz try/catcha (nie polecam)
- Rejestracja:ponad 9 lat
- Ostatnio:4 miesiące
- Postów:2787
Niniejszy temat dobitnie pokazuje, jak trudno jest określić, czy SOLID został złamany czy nie :) Tak jak pisałem tutaj:
https://4programmers.net/Forum/Kariera/344443-wydajnosc_programisty?p=1710837#id1710837

ImmutableList
z samymi aksesorami oraz MutableList
które ma dodatkowo metody mutujące i problem by się rozwiązał? ;)


MutableList to takie ImmutableList tylko ze z metodami mutujacymi
tez brzmi absurdalnie ale nie rodzi problemow.


- Rejestracja:ponad 12 lat
- Ostatnio:około 8 godzin
- Postów:3563
krancki napisał(a):
No i pytanie czy to łamie zasade liskov? Z jednej strony dokumentacja mówi że metoda add może rzucić wyjątkiem i jest to oczekiwany efekt.
To łamie zasadę podstawienia Liskovej i jest głupie. Przy czym od razu zaznaczę, że nie każde złamanie zasady SOLID czy DRY jest głupie - chociaż muszę przyznać, że zdecydowana większość takich przypadków do czegoś co nazywam "świadomym łamaniem zasad" nie należy.
Ale od razu nasuwa mi się pytanie, to po co jest metoda która może rzucać taki wyjątek. Jeżeli ktoś operuje na liście, to co ma zawsze się zabezpieczać przed tym że ktoś może wstawić niemutowalną kolekcje (wiadomo modyfikowanie listy jest bee, ale załóżmy taki przypadek).
Jest dokładnie w drugą stronę. To ludzie implementujący interfejs List powinni się zatroszczyć o to, żeby zachować zasadę podstawienia. Ludzie korzystający z tego interfejsu nie muszą się zabezpieczać bo przyjmują, że ta zasada jest spełniona. W praktyce oznacza to tyle, że nie wypycha się takich "lewych" instancji poza jakiś konkretny scope.
I tak już poza dla tych wszystkich, którzy twierdzą że zasada podstawienia nie jest złamana - taki teścik:
void clearAndAdd(final List<Object> list) {
list.clear();
list.add(new Object());
Assert.assertEquals(1, list.size());
}
w zależności od implementacji się wykrzaczy - ergo zasada LSP jest złamana.

- Rejestracja:prawie 20 lat
- Ostatnio:32 minuty
Gdybyście mieli interfejs:
interface Thrower {
void maybeThrow();
}
którego implementacje w 99.999% przypadków nie rzucają exceptionami to stwierdzilibyście, że implementacja, która faktycznie rzuca wyjątkiem, łamie zasadę LSP?
Jeśli chodzi o hierarchię typów zawierającą zarówno kolekcje mutowalne jak i niemutowalne to jedynym językiem, którego znam (ale w stosunkowo niewielu językach to drążyłem) gdzie to jest sensownie rozwiązane jest Scala gdzie mamy:
- wspólne typy bazowe z operacjami mającymi sens zarówno dla kolekcji mutowalnych jak i niemutowalnych: https://www.scala-lang.org/api/current/scala/collection/index.html
- kolekcje niemutowalne implementujące typy bazowe: https://www.scala-lang.org/api/current/scala/collection/immutable/index.html
- kolekcje mutowalne implementujące typy bazowe: https://www.scala-lang.org/api/current/scala/collection/mutable/index.html

- Rejestracja:prawie 7 lat
- Ostatnio:dzień
- Lokalizacja:Kraków
- Postów:1999
Wibowit napisał(a):
Gdybyście mieli interfejs:
interface Thrower { void maybeThrow(); }
którego implementacje w 99.999% przypadków nie rzucają exceptionami to stwierdzilibyście, że implementacja, która faktycznie rzuca wyjątkiem, łamie zasadę LSP?
Jeśli pozostałe implementacje by design
mają nie rzucać wyjątkami, a jedna z nich będzie by design
rzucać wyjątkiem bo na niej nie wolno akurat tej metody wywołać, to jak najbardziej tak.

- Rejestracja:prawie 20 lat
- Ostatnio:32 minuty
Zasada LSP jest relacją rodzic-dziecko, a nie grupa-element:
Subtype Requirement: Let {\displaystyle \phi (x)}\phi (x) be a property provable about objects {\displaystyle x}x of type T. Then {\displaystyle \phi (y)}{\displaystyle \phi (y)} should be true for objects {\displaystyle y}y of type S where S is a subtype of T.
W skrócie: jeśli coś da się udowodnić dla nadtypu to powinno zachodzić dla podtypu. To, że 99.999% implementacji zachowuje się w dany sposób nie ma żadnego wpływu na zachodzenie LSP. Analizując typ T i jego podtyp S wszystkie inne podtypy T nie mają znaczenia. Kodowanie do interfejsu polega na kodowaniu do kontraktu zawartego w interfejsie, a nie w 99.999% jego implementacji.


- Rejestracja:ponad 8 lat
- Ostatnio:około godziny
Ale to jest wlasnie to o czym mowilem. Ktos napisal kod, ktory najpierw zlamal ISP a w efekcie tego poszlo jeszcze LSP, wiec sobie dopisal do dokumentacji ze niektore implementacje moga rzucic wyjatkiem. No i takim o to sposobem "nie ma problemu bo wszystko udokumentowane".
Tylko ze kazdy szajs mozna udokumentowac.

- Rejestracja:prawie 7 lat
- Ostatnio:dzień
- Lokalizacja:Kraków
- Postów:1999
stivens napisał(a):
Ale to jest wlasnie to o czym mowilem. Ktos napisal kod, ktory najpierw zlamal ISP a w efekcie tego poszlo jeszcze LSP, wiec sobie dopisal do dokumentacji ze niektore implementacje moga rzucic wyjatkiem.
Notabene, nie znam zaszłości historycznych także mogę się mylić, ale jeśli metoda j.u.List#add
wcześniej nie przewidywała rzucenia wyjątku, a następnie po pojawieniu się niemutowalnych list zaczęła go przewidywać, to strzelam że dodatkowo złamane zostało też OCP. Nawet jeśli uznamy, że dokumentacją możemy "odłamać" LSP.
No i takim o to sposobem "nie ma problemu bo wszystko udokumentowane".
Tylko ze kazdy szajs mozna udokumentowac.
To. Jeśli docsy mają robić za perfumę maskującą dowolny code smell, to praktycznie jakiekolwiek regułki i zasady można wyrzucić do kosza - jako, że każde złamanie można i tak uzasadnić i unieważnić :P

- Rejestracja:prawie 20 lat
- Ostatnio:32 minuty
Ktos napisal kod, ktory najpierw zlamal ISP a w efekcie tego poszlo jeszcze LSP
Gdyby jedna reguła implikowała drugą to po co mieć dwie reguły? Reguły są ortogonalne jak parzystość i dodatniość.
wiec sobie dopisal do dokumentacji ze niektore implementacje moga rzucic wyjatkiem
List.add
od początku było opcjonalne i miało wyspecyfikowane @throws UnsupportedOperationException if the <tt>add</tt> method is not supported by this list.
Widać to np na: https://github.com/fanhongtao/JDK/blob/jdk_1.2.1/src/java/util/List.java#L147-L169 Później doszło nawet Throws: NullPointerException - if the specified element is null and this list does not permit null elements
.

Reguły są ortogonalne jak parzystość i dodatniość.
Gorzej jest, niektóre reguły są sprzeczne :( Pisał o tym Uncle Bob chyba w czystej architekturze

- Rejestracja:ponad 8 lat
- Ostatnio:około godziny
Ja nie napisalem o logicznej implikacji tylko o relacji przyczynowo-skutkowej.
Mogloby byc i od poczatku. Ktos sie zorientowal, ze nie kazda lista bedzie to obslugiwac ale zamiast wywalic metode na etapie definicji to dorzucil dokumentacje.

- Rejestracja:prawie 20 lat
- Ostatnio:32 minuty
Mogloby byc i od poczatku. Ktos sie zorientowal, ze nie kazda lista bedzie to obslugiwac ale zamiast wywalic metode na etapie definicji to dorzucil dokumentacje.
Obstawiam, że to nie było łatanie po fakcie, tylko zamierzony efekt. Prawdopodobnie jeden człowiek projektował zarówno metodę List.add
jak i implementacje, w których metoda add
"nie działa":
- https://github.com/fanhongtao/JDK/blob/jdk_1.2.1/src/java/util/List.java
@author Josh Bloch, @since JDK1.2
- https://github.com/fanhongtao/JDK/blob/jdk_1.2.1/src/java/util/Collections.java
@author Josh Bloch, @since JDK1.2
Taki C# skopiował ten design nawet do generycznych kolekcji mimo, iż MS zdecydował się na zerwanie kompatybilności wstecznej podczas wprowadzania genericsów:
https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.icollection-1.add?view=netcore-3.1
Exceptions
NotSupportedException
The ICollection<T> is read-only.
Exceptions
NotSupportedException
The IDictionary<TKey,TValue> is read-only.
Exceptions
NotSupportedException
The IList<T> is read-only.
Ale już np dla ISet<T>.add
https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.iset-1.add?view=netcore-3.1 nie rzuca wyjątkiem.



- Rejestracja:ponad 8 lat
- Ostatnio:około godziny
Obstawiam, że to nie było łatanie po fakcie, tylko zamierzony efekt. Prawdopodobnie jeden człowiek projektował zarówno metodę List.add jak i implementacje, w których metoda add "nie działa":
A w jaki sposob jest to sprzeczne? Bo mnie to nawet nie dziwi, ze ktos sam sobie takiego hot-fixa wymyslil :)
Ktos sie zorientowal, ze nie kazda lista bedzie to obslugiwac ale zamiast wywalic metode na etapie definicji to dorzucil dokumentacje.
Kopiowanie glupich rozwiazan przez MS juz mnie bardziej dziwi
- Rejestracja:ponad 12 lat
- Ostatnio:około 8 godzin
- Postów:3563
Wibowit napisał(a):
Zasada LSP jest relacją rodzic-dziecko, a nie grupa-element:
Subtype Requirement: Let {\displaystyle \phi (x)}\phi (x) be a property provable about objects {\displaystyle x}x of type T. Then {\displaystyle \phi (y)}{\displaystyle \phi (y)} should be true for objects {\displaystyle y}y of type S where S is a subtype of T.
W skrócie: jeśli coś da się udowodnić dla nadtypu to powinno zachodzić dla podtypu. To, że 99.999% implementacji zachowuje się w dany sposób nie ma żadnego wpływu na zachodzenie LSP. Analizując typ T i jego podtyp S wszystkie inne podtypy T nie mają znaczenia.
W tym konkretnym przypadku zasada LSP jest złamana. Na początek - rozważmy List.add(index, element) dla standardowej biblioteki Javy.
W Javie istnieje klasa AbstractList której ta metoda add wygląda tak:
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
Natomiast ArrayList (lub choćby) która dziedziczy po AbstractList już tego wyjątku nie rzuca. Więc podtyp ArrayList zachowa się inaczej niż AbstractList. Sam interfejs List nijak się oczywiście ma do LSP ponieważ metoda add(index, element) nie jest nawet zdefiniowana dla interfejsu List. Przy czym przyznaję, że od razu przeskoczyłem do rozmowy o konkretnych implementacjach w Javie, trzeba było to napisać jaśniej.

- Rejestracja:prawie 20 lat
- Ostatnio:32 minuty
https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html#add-E-
Note that this implementation throws an UnsupportedOperationException unless add(int, E) is overridden.
Co możesz udowodnić dla AbstractList.add
co nie zachodzi dla podtypów AbstractList
?
W ogólności budowanie własnej wizji kontraktu przez analizowanie szczegółów implementacyjnych czegoś co ma jasny kontrakt jest programistycznym rakiem. Dla przykładu programiści Javy polegają na konkretnym porządku iteracji w HashMapach, co jest absurdem. Jeśli przez 10 lat HashMapa iterowała obiekty w takiej a takiej kolejności to jeśli w nowej wersji Javy kolejność iteracji się zmieni to można traktować to jako błąd? Niestety mimo iż oficjalny kontrakt niczego nie gwarantuje to jednak oczekiwania Javowców, którzy nie potrafią kodować do interfejsu sprawia, że de facto Java jest związana na stałe z czymś co hamuje optymalizacje wydajnościowe. Szczegóły od momentu 42:54 do ok 47:00:
Z hashCode
zepsuli jeszcze bardziej bo szczegóły implementacyjne wstawili w dokumentację (np dla kolekcji czy stringów) i już nawet z tego powodu nie da się poprawić hashCode'ów. Stąd pomysł nowej metody Object.longHashCode
, której implementacje w standardowych typach już nie będą miały w kontrakcie szczegółów implementacyjnych, a wręcz współczynniki hashowania będą się zmieniać co uruchomienie JVMki: https://openjdk.java.net/jeps/8201462

values()
zwraca wartości w innej kolejności. W zespole obok mieli przez to problemy bo źle napisane testy przestały przechodzić

- Rejestracja:ponad 12 lat
- Ostatnio:około 8 godzin
- Postów:3563
Wibowit napisał(a):
https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html#add-E-
Note that this implementation throws an UnsupportedOperationException unless add(int, E) is overridden.
Co możesz udowodnić dla
AbstractList.add
co nie zachodzi dla podtypówAbstractList
?
Tak jak napisałeś - to jest relacja rodzic-dziecko. Czyli konkretne dziecko (ArrayList) ma konkretny konflikt z rodzicem (AbstractList) i w tym momencie LSP jest złamane. W tym konkretnym przypadku:
- dla dowolnej pary {index, element} AbstractList.add(index, element) zawsze zwraca UnsupportedOperationException
- dla dowolnej pary {index, element} ArrayList.add(index, element) nigdy nie zwraca UnsupportedOperationException
W ogólności budowanie własnej wizji kontraktu przez analizowanie szczegółów implementacyjnych czegoś co ma jasny kontrakt jest programistycznym rakiem.
Pełna zgoda. Ale kontrakt, a LSP to nie są pojęcia tożsame.
- Rejestracja:prawie 18 lat
- Ostatnio:29 dni
Nie łamie. LSP mówi o kontrakcie, nie o implementacji, a "sensowność" kontraktu nie ma znaczenia. To nie znaczy, że List.add jest dobrze zaprojektowaną metodą, ale nie jest to argument, że łapie to LSP.
Wibowit napisał(a):
Co możesz udowodnić dla
AbstractList.add
co nie zachodzi dla podtypówAbstractList
?
Bardzo dobre, ale i odrobinkę podchwytliwe pytanie, co można udowodnić dla X i co nie zachodzi dla Y. Ogólnie zasada LSP w literalnym znaczeniu nie ma za dużo sensu. W swojej podstawowej formie mówi ona, że jeżeli da się udowodnić jakieś p dla typu bazowego, to p musi być spełnione dla typu pochodnego, ale zawsze mogę przyjąć, że moim p dla typu bazowego (o ile ma on implementację) jest dokładnie ta implementacja, co wyklucza jakiekolwiek modyfikowanie zachowania w podtypie.
Oczywistym jest, że nie o takie rozumienie LSP chodzi, więc trzeba uważać, jakie p rozważamy, a ten zbiór rozważanych p, to właśnie dobrze określony kontrakt.
- Rejestracja:ponad 12 lat
- Ostatnio:około 8 godzin
- Postów:3563
Wibowit napisał(a):
Pełna zgoda. Ale kontrakt, a LSP to nie są pojęcia tożsame.
Jeśli LSP mówi o tym, by kodować do implementacji, a nie interfejsów to jest to reguła do wyrzucenia.
LSP nie wypowiada się na ten temat kontraktów i nie ma nic wspólnego z kontraktami. Jeśli metoda jest abstrakcyjna to LSP się jej nie tyczy bo takiej metody po prostu nie ma.
Tak jak napisałeś:
W skrócie: jeśli coś da się udowodnić dla nadtypu to powinno zachodzić dla podtypu
W tym konkretnym przypadku w Javie wyszła słabość języka, które łatano klasami AbstractXXX i błędnie to zrealizowano . Natomiast sama zasada LSP broni przed takimi potworkami (zdarzają się częściej niż mogłoby się wydawać):
class Product {
private Integer id;
private String name;
Integer getId() {
return id;
}
String getName() {
return name;
}
}
interface ProductSorter {
/**
* Returns list of sorted products
* @param products
* @return
*/
Collection<Product> sort(Collection<Product> products);
}
class ByNameProductSorter implements ProductSorter {
/**
* Returns collection of products sorted by name
* @param products
* @return
*/
@Override
public Collection<Product> sort(Collection<Product> products) {
return products.stream()
.sorted(Comparator.comparing(Product::getName))
.collect(Collectors.toList());
}
}
class ByIdProductSorter extends ByNameProductSorter {
/**
* Returns collection of products sorted by id
* @param products
* @return
*/
@Override
public Collection<Product> sort(Collection<Product> products) {
return products.stream()
.sorted(Comparator.comparing(Product::getId))
.collect(Collectors.toList());
}
}
Zgadzam się z tym, że przy poprawnie definiowanych kontraktach LSP pozostanie zachowane. Natomiast problemy zaczynają się, gdy chcemy zdefiniować bardzo ogólny kontrakt - wtedy jest bardzo trudno przewidzieć wszystkie możliwe sytuacje.

- Rejestracja:ponad 8 lat
- Ostatnio:około godziny
- Lokalizacja:U krasnoludów - pod górą
- Postów:4707
Wibowit napisał(a):
Z
hashCode
zepsuli jeszcze bardziej bo szczegóły implementacyjne wstawili w dokumentację (np dla kolekcji czy stringów) i już nawet z tego powodu nie da się poprawić hashCode'ów. Stąd pomysł nowej metodyObject.longHashCode
, której implementacje w standardowych typach już nie będą miały w kontrakcie szczegółów implementacyjnych, a wręcz współczynniki hashowania będą się zmieniać co uruchomienie JVMki: https://openjdk.java.net/jeps/8201462
Sorry za off-topic, ale nie mogę jaki paździerz. Nie wiedziałem o tym.
Otóż największym IMO problemem hashCode jest to, że należy on do Object.
To powinnien być osobny interfejs - podobnie jak Comparator.
Nie ma tak, że dany typ ma jeden idealny hashCode - bo to zależy jakie obiekty wkładamy do np. konkretnego HashMap.
Raz będzie ich mało i będzie zależeć nam na szybkiej implementacji, raz będzie mnóstwo i będziemy chcieli mniej kolizji..
czasem wystarczy w stringu pierwsza litera jako hashCode, czasem pierwsze 3 - a czasem trzeba brać implementację po wszystkim (jak obecnie).
Dla tego sameto typu możemy chcieć miec w jednym programie kilka różnych hashCode implementacji... podobnie jak z Comparator.
To oczywiscie mniej częsty case niż z Comparatorem, bo raczej będzie dotyczył programów, gdzie walczymy o cykle - ale jednak.
Przy okazji osobną wadą obecnego podejścia- implicit hashCode, jest fakt, że wiele osób nie ma pojęcia o istnieniu problemu i mamy potem dziwne bugi na produkcji.
Albo mamy durne kłótnie o implementacje hashCode
na etapie tworzenia klasy. Jeśli masz klasę i wszystkie pola .. to nadal nie wiadomo jaki hahsCode jest optymalny, bo to nie od klasy zależy, ale od tego gdzie hashCode będzie używany.
W tym kontekście nowy hashCode
, który powtarza największy IMO błąd poprzedniego designu to niezła $#23@#.



- Rejestracja:ponad 12 lat
- Ostatnio:około 8 godzin
- Postów:3563
Wibowit napisał(a):
Jeśli metoda jest abstrakcyjna to LSP się jej nie tyczy bo takiej metody po prostu nie ma.
List.add
jest abstrakcyjna. Czy w takim razie LSP jej nie dotyczy? Hmm, to po co cały ten wątek?
Tak jak napisałem wcześniej:
- Bardzo niejawnie przeszedłem z rozmowy o interfejsie na temat rozmowy o konkretnych implementacjach
- Nie dotyczy. Metoda abstrakcyjna nie jest stricte własnością obiektu, jedynie znacznikiem, że każdy obiekt implementujący coś takiego powinien taką własność mieć.
- Wątek jest w celu wyjaśnienia wątpliwości.
- Rejestracja:prawie 18 lat
- Ostatnio:29 dni
wartek01 napisał(a):
- Nie dotyczy. Metoda abstrakcyjna nie jest stricte własnością obiektu, jedynie znacznikiem, że każdy obiekt implementujący coś takiego powinien taką własność mieć.
Oczywiście, że dotyczy. LSP mówi o kontrakcie, w tym przypadku o sygnaturze metody, konwencji wywołania, parametrach i całej reszcie części technicznej. To jest świetny przykład, bo tutaj kompilator broni programisty przed złamaniem LSP, ale w innych językach tak nie musi być — metoda może nie istnieć, metoda może mieć inną konwencję wywołania i rzuci segfault, metoda może zostać inaczej "zmanglowana" przez kompilator i w efekcie będzie miała inną nazwę symboliczną.
- 1
- 2