@Pebal: Uciekasz w metafory tak daleko, że tracisz kontakt z docelowym zagadnieniem. Samolot może stracić skrzydło w wyniku niemożliwych do przewidzenia i zapobieżenia czynników, ale niemal zawsze nie ma to miejsca w przypadku obiektów — obiekt nie przestanie nagle i samoistnie potrafić próbować łączyć się z bazą danych (co najwyżej może mu się to przestać udawać, ale nie zatraci tej metody); nie straci wiedzy, ile wartości przechowuje; pozostanie sensowne odpytywanie o uprawnienia danego użytkownika…
Rzadko ma sens tworzenie klasy Samolot
, która może nie mieć skrzydeł — to nakłada na użytkownika niepotrzebny narzut mentalny, wymusza każdorazowe sprawdzanie, czy tym Samolotem można zrobić to, co się zamierzyło. To tak, jakbyś chciał wynająć ten samolot, żeby gdzieś polecieć, firma wynajmująca ci podsunie umowę do podpisania i radośnie wręczy „kluczyki” do takiego bez skrzydeł — no co, w końcu to dalej jest samolot, a że bezużyteczny?
W Twoim interesie, jako osoby projektującej jakąś klasę, jest dostarczenie klasy, która robi to, co sugeruje jej nazwa. Jeśli wiesz, że czasem nie jesteś, to nie zwracaj Samolot
u, tylko Maybe<Samolot>
, czy jaką tam masz konstrukcję w języku — w każdym razie, wyraźnie zasugeruj, że możesz otrzymać coś, co nie spełnia zamierzeń.
Bo w przeciwnym wypadku będziesz operował na mocno niepewnych obiektach — zatem naklepiesz bardzo dużo kodu, który się co chwila upewnia, że z instancjami wszystko dalej OK, a napisany „oczywisty” kod, który tego nie robi, będzie błędny.
No i wreszcie — odwracasz kota ogonem. Nie ma nic złego w tym, że Prostokąt
będzie kwadratem. Jest natomiast sporo złego, gdy nagle Kwadrat
okazuje się być Prostokąt
em… Nikt Ci nie mówi, że „każda instancja powinna być obiektem klasy tak nisko w łańcuchu dziedziczenia, jak się da”. Zasada Liskow to nie to, tylko „gdy klasa potomna jest traktowana jako klasa nadrzędna, wszystkie jej metody i atrybuty muszą mieć sens dla osoby, która nie wie, do jakiej klasy należą”.
Czyli jak masz coś, co jest instancją klasy, która dziedziczy po Prostokąt
, to jej długość
musi oznaczać długość, szerokość
— szerokość, a .pole()
ma liczyć pole. Nie interesuje cię w tym momencie, czy to jest kwadrat czy nie, tylko czy można z klas potomnych korzystać jak z klas bazowych, a nie okaże się, że nagle .pole()
mi liczy obwód…