ummm.. po co dziedziczyć skoro można implementować wspólny interface?
Czym jest implementacja wspólnego interfejsu jak nie pewnym rodzajem dziedziczenia? Czy raczej odwrotnie - dziedziczenie po klasach zakłada, że klasa potomna będzie zawierała implementację interfejsu klasy bazowej (ponieważ go odziedziczy). Jak dla mnie prawo Liskov dotyczy również/właśnie interfejsów.(gdzie przez interfejs rozumiem koncepcję programistyczną, niezależnie czy w języku osiągniemy to przez słówko "interface", klasę abstrakcyjną czy przez kacze typowanie.
po co rectangle/square jest mutowalne?
Use case mutowalnego kwadratu/prostokątu to np. program graficzny, w którym można byłoby tworzyć swoje prostokąty i za pomocą myszą zmieniać wielkość.
Oczywiście, pewnie dałoby się to zrobić funkcyjnie, niemutując niczego, tylko tworząc nowe figury za każdą zmianą - ale z drugiej strony można to zrobić właśnie w taki mutowalny sposób. Mutowalny kod nie musi być zawsze "zły" (dużo też zależy od subiektywnych poglądów. Zwolennikom programowania funkcyjnego w głowie się nie mieściłoby, żeby cokolwiek mutować).
po co wystawia jawnie swoje składowe?
To akurat nie ma nic do rzeczy. Przykład dotyczył settera jeśli dobrze pamiętam, ale przecież równie dobrze mogło dotyczyć to każdej metody, która mutuje obiekt.
Chociaż... zastanawiam się co jeślibyśmy poszli w programowanie funkcyjne i nigdy byśmy nie mutowali żadnych obiektów. Też myślę, że można by złamać zasadę Liskov. Niech by jakaś metoda zwracała co innego niż metoda bazowa (np. inny zwracany typ, gdzie np. w klasie bazowej szerokość zwracana by była jako liczba (100 pikseli = 100
), a w klasie potomnej jako string (100 pikseli = "100px"
). I już to by wystarczyło, żeby rozwalić kod, który oczekuje liczby.
Swoją drogą czym się różni zasada Liskov od Open Closed Principle? Można przecież argumentować, że wynika ona z OCP, i że klasy powinny być otwarte na rozszerzanie ("dziedziczenie" w tym przypadku, i np. dodawanie nowych metod) ale zamknięte na modyfikację (czyli na niekompatybilną wstecz zmianę zachowania).
wg mnie to słaby i naciągany przykład; Javascriptowco nie masz jakiś praktycznych przykładów z pracy bądź własnych projektów?
Załóżmy, że (fikcyjna sytuacja, ale podobna do tych, które faktycznie się zdarzały) wchodzę do projektu reactowego, w którym są jakieś komponenty-widżety. Np. jest jakiś kustomowy komponent Carousel
, który przyjmuje właściwości typu imageList
, animationDelay
, onSlideChange
czy showButtons
albo additionalStyles
).
I ten komponent ma jakieś bugi do naprawienia i mam się nimi zająć. I tak, patrząc na źródło tego komponentu dochodzę do wniosku, że lepiej napisać widżet od nowa, niż poprawiać tonu kodu spaghetti.
Problem tylko, że komponent Carousel
jest używany w 50 miejscach w projekcie, na dodatek w 10 miejscach w innym projekcie pisanym przez kogoś innego.
Czyli co? w 50 miejscach będę musiał zmieniać kodzik na nową implementację oraz powiedzieć drugiemu programiście, żeby zmienił sobie kod w 10 miejscach? Niekoniecznie. Mogę zrobić tak, że napiszę komponent, który jest całkowicie kompatybilny z poprzednią, przyjmuje takie same właściwości, "to samo robi" ( z perspektywy biznesowej) jednak będzie mieć ładny kod i dobre działanie bez bugów. I zachowam prawo Liskov.
Łamiąc to prawo mógłbym tak zrobić np., że Widget byłby niekompatybilny i miałby inne właściwości. Tym sposobem kod, który z niego by korzystał, mógłby np. przestać go wyświetlać (czasami to lepsze, złamać kompatybilność wsteczną i stworzyć nowe API - ale mimo wszystko breaking changes są bolesne).