Kiedy używać dziedziczenia, kiedy używać „tylko pola logicznego”?

18

W naszej aplikacji Rails dodajemy powiadomienia. Niektóre z nich to blocking: Zatrzymują postęp dodawanego zasobu, ponieważ brakuje niektórych informacji o tym zasobie.

Inne powiadomienia są prostymi powiadomieniami i zawierają jedynie informacje.

Dzisiaj rozmawiałem z innym programistą w naszym zespole. Utworzyłem następującą strukturę dziedziczenia:

wprowadź opis zdjęcia tutaj

Wolałby jednak, abym po prostu dodał blockingjako metodę zwracającą wartość logiczną w każdym powiadomieniu i określił listę podklas, które blokują wewnątrz nadrzędnej klasy Powiadomienia.

Różnica między tymi podejściami nie jest bardzo duża; w moim podejściu nie trzeba określać tej listy, utrzymując klasę root czystszą. Z drugiej strony, specjalna logika, która dzieje się Notification::Blockingteraz, również nie jest bardzo duża.

Jaka abstrakcja jest bardziej odpowiednia dla tego problemu?

Qqwy
źródło
11
Klasa rodzicielska nigdy nie powinna wiedzieć o swoich dzieciach. Dlaczego musisz prowadzić listę podklas?
coteyr
Jak są dodawane do zasobu i jak zatrzymują jego postęp?
null
3
Dlaczego potrzebujesz tak wielu klas powiadomień? Wydaje mi się, że możesz utworzyć jedną klasę powiadomień, a następnie pozwolić, aby dane sterowały działaniami zamiast typów danych.
Trisped
1
@Trisped: racja, jeśli uważasz, że przenosisz jeden aspekt zachowania do klasy podstawowej z wyczerpującą listą przypadków, to miej odwagę w swoich przekonaniach, przyznaj, że tak naprawdę nie projektujesz użytecznej klasy podstawowej do dostosowania przez rozszerzenie i przenieś całe zachowanie do klasy podstawowej!
Steve Jessop

Odpowiedzi:

35

Chcesz uniknąć wiedzy klas podstawowych o klasach pochodnych. Wprowadza ścisłe sprzężenie i jest bólem konserwacyjnym, ponieważ musisz pamiętać o dodawaniu do listy za każdym razem, gdy tworzysz nową klasę pochodną.

Zapobiegnie to również możliwości umieszczenia klasy Notification w pakiecie / zestawie wielokrotnego użytku, jeśli chcesz użyć tej klasy w wielu projektach.

Jeśli naprawdę chcesz użyć jednej klasy bazowej, innym sposobem na rozwiązanie tego jest dodanie wirtualnej właściwości lub metody IsBlocking w podstawowej klasie powiadomień. Klasy pochodne mogą następnie zastąpić to, aby zwrócić wartość prawda lub fałsz. Miałbyś rozwiązanie jednej klasy bez wiedzy klasy podstawowej o klasach pochodnych.

17 z 26
źródło
3
To. Podejmuj decyzje w jednym miejscu. Nie rozpraszaj wiedzy o tym, które klasy blokują między klasą a listą.
candied_orange
Ten, który robię, działa bardzo dobrze i jest bardzo wielokrotnego użytku.
coteyr
13

i podaj listę podklas, które blokują wewnątrz nadrzędnej klasy Powiadomienia.

Wygląda to bardzo osobliwie i jest szczególnym zapachem kodu.

Podałbym podklasy, jeśli masz różnice w zachowaniu między klasami i chcesz traktować wszystkie te powiadomienia w ten sam sposób (tj. Używając polimorfizmu ).

Brian Agnew
źródło
1
Myślę, że „zachowanie” jest tutaj kluczowe: gdy są to tylko dane, pole powinno być wystarczającym czynnikiem dyskryminującym. Zachowanie jest lepszym powodem do zastosowania polimorfizmu, jednak przy tworzeniu hierarchii dziedziczenia zawsze należy brać pod uwagę złożoność utrzymania. Przeczytaj en.wikipedia.org/wiki/Composition_over_inheritance
cottsak
7

W przeciwieństwie do istniejących odpowiedzi, sugerowałbym, że właściwość boolean jest najlepszą opcją, jeśli tryb, który ma być użyty, wymaga dynamicznej zmiany (np. Za pomocą pliku konfiguracyjnego, który podaje listę typów, które mają być blokowane a które nie są).

To powiedziawszy, lepszym projektem nawet w tej sytuacji może być użycie obiektu Decorator.

Jules
źródło
1

Powiedziałbym, że zależy to od tego, ile jeszcze wyjątkowych jest w blokowaniu powiadomień, chociaż moją pierwszą myślą jest użycie „obu”:

class Notification
 virtual Boolean Blocking{get return false;}

class BlockingNotification inherits Notification
 virtual overrides Boolean Blocking{get return true;}

W ten sposób możesz użyć n.Blockinglub n is BlockingNotification(wszystko w pseudokodzie), chociaż jeśli chcesz pozwolić klasie na implementację wartości kontekstowej Blocking, ponieważ będziesz musiał sprawdzać tę wartość za każdym razem, BlockingNotificationklasa staje się mniej przydatne.

W każdym razie zgadzam się z innymi odpowiedziami, że nie chcesz, aby implementacja klasy podstawowej Blockingmusiała wiedzieć o klasach pochodnych.

Mark Hurd
źródło
0

Zamiast tworzyć dwie klasy podstawowe i wiele wystąpień każdej z nich, utwórz jedną klasę powiadomień z wartością logiczną, aby wskazać, czy powiadomienie blokuje i wszelkie inne informacje potrzebne do przekazania powiadomienia użytkownikowi.

Pozwala to na użycie jednego zestawu kodu do przetwarzania i prezentacji powiadomień oraz zmniejsza złożoność kodu.

Trisped
źródło