Czy klasa powinna wiedzieć o swoich podklasach? Czy klasa powinna na przykład zrobić coś specyficznego dla danej podklasy?
Mój instynkt podpowiada mi, że jest to zły projekt, wydaje się być pewnego rodzaju anty-wzorem.
Czy klasa powinna wiedzieć o swoich podklasach? Czy klasa powinna na przykład zrobić coś specyficznego dla danej podklasy?
Mój instynkt podpowiada mi, że jest to zły projekt, wydaje się być pewnego rodzaju anty-wzorem.
Odpowiedzi:
Odpowiedź sugerowana przez pojęcie klas brzmi „nie”.
Niezależnie od tego, jaką akcję, dane lub relację wykonujesz, należy ona do wszystkich podklas - wtedy powinna być obsługiwana w nadklasie bez sprawdzania rzeczywistego typu. Lub odnosi się tylko do niektórych podklas - wtedy trzeba by wykonać sprawdzanie typu w czasie wykonywania, aby zrobić właściwą rzecz, nadklasa musiałaby zostać zmieniona za każdym razem, gdy ktoś inny odziedziczy po niej (lub może po cichu zrobić coś niewłaściwego), zmiany w klasach pochodnych mogą złamać niezmienioną nadklasę itp.
Krótko mówiąc, masz szereg złych konsekwencji, które zwykle są wystarczająco złe, aby odrzucić takie rozwiązania z ręki. Jeśli kilka podklas robi to samo i chcesz uniknąć powielania kodu (praktycznie zawsze jest to dobra rzecz), lepszym rozwiązaniem jest wprowadzenie klasy średniej, z której wszystkie te podklasy mogą odziedziczyć kod.
Nie tylko nie powinien wiedzieć, ale po prostu nie może ! Zazwyczaj zajęcia mogą być przedłużane w dowolnym miejscu i czasie. Można go rozszerzyć o klasy, które nawet nie istniały, kiedy zostały napisane.
Niektóre języki pozwalają na kontrolowanie rozszerzania klas przez nadklasę. W Scali klasę można oznaczyć jako sealed
, co oznacza, że można ją rozszerzyć tylko o inne klasy w tej samej jednostce kompilacji (plik źródłowy). Jednakże, chyba że te podklasy są również sealed
lub final
, podklasy można następnie rozszerzyć o inne klasy.
W Scali służy do modelowania zamkniętych typów danych algebraicznych, więc kanoniczny List
typ Haskell :
data List a = Nil | Cons a (List a)
można modelować w Scali w następujący sposób:
sealed trait List[+A]
case object Nil extends List[Nothing]
final case class Cons[+A] extends List[A]
I można zagwarantować, że tylko opuszczeniu w tych dwóch podsystem „klas”, bo List
jest sealed
i nie może być przedłużony poza pliku, Cons
jest final
i nie może być rozszerzony na wszystkich i Nil
to object
, które nie mogą zostać rozszerzone tak.
Ale jest to szczególny przypadek użycia (modelowanie algebraicznych typów danych poprzez dziedziczenie) i nawet w tym przypadku nadklasa tak naprawdę nie wie o swoich podklasach. Jest to bardziej gwarancja dla użytkownika tego List
typu, że jeśli podejmie dyskryminację Nil
i Cons
nie będzie żadnej innej alternatywy wyskakującej za jego plecami.
abstract internal
członkowskich.
Prostą odpowiedzią jest: nie.
Sprawia, że kod jest kruchy i unieważnia dwie podstawowe zasady programowania obiektowego.
Tak czasami. Na przykład, gdy istnieje ograniczona liczba podklas. Odwiedzający jest ilustracją użyteczność tego podejścia.
Przykład: abstrakcyjne węzły drzewa syntaktycznego (AST) o dobrze zdefiniowanej gramatyce mogą być dziedziczone z pojedynczej Node
klasy implementującej wzorzec gościa do obsługi wszystkich typów węzłów.
Node
Klasa podstawowa oczywiście nie zawiera żadnych bezpośrednich odniesień do jej podklas. Ale zawiera accept
metodę z Visitor
parametrem. I Visitor
zawiera visit
metodę dla każdej podklasy. Mimo że Node
nie ma bezpośrednich odniesień do swoich podklas, „wie” o nich pośrednio, poprzez Visitor
interfejs. Wszyscy przez to połączyli się.
Jeśli piszę komponent dla firmy, a później, po odejściu, ktoś rozszerza go na własne potrzeby, czy powinienem zostać o tym poinformowany?
Nie!
To samo z klasami. Zaufaj instynktowi.