Czy wielokrotne dziedziczenie narusza zasadę pojedynczej odpowiedzialności?

18

Jeśli masz klasę, która dziedziczy z dwóch odrębnych klas, czy nie oznacza to, że twoja podklasa automatycznie (przynajmniej) 2 rzeczy, po jednej z każdej nadklasy?

Uważam, że nie ma różnicy, jeśli masz wiele interfejsów dziedziczenia.

Edycja: Dla jasności uważam, że jeśli podklasowanie wielu klas narusza SRP, to implementacja wielu interfejsów (niemarkerowych lub podstawowego interfejsu (np. Porównywalny)) również narusza SRP.

m3th0dman
źródło
Żeby było jasne, czy uważasz, że implementacja wielu interfejsów narusza SRP?
Uważam, że jeśli podklasowanie wielu klas narusza SRP, to implementacja wielu (niemarkerowych) interfejsów również narusza SRP.
m3th0dman
Głosowałbym za tym pytaniem, ale ty włączyłeś swoją opinię do pytania, z którym się nie zgadzam.
Nicole,
1
@NickC: Nie wyraziłem swojej opinii (powinieneś przeczytać powyższy komentarz); Przeredaguję pytanie, aby było bardziej jasne.
m3th0dman
1
@NickC Nie mam zdania, dlatego zapytałem. Powiedziałem tylko, że oba są ze sobą powiązane (dziedziczenie interfejsu i dziedziczenie klas); jeśli narusza dziedziczenie klas, to również narusza dziedziczenie interfejsu, jeśli nie dotyczy jednego, to nie narusza również drugiego. I nie obchodzi mnie głosowanie na górę ...
m3th0dman

Odpowiedzi:

17

W bardzo wąskim znaczeniu odpowiedź brzmi „Tak”: zakładając, że twoje podstawowe klasy lub interfejsy są zaprojektowane do jednego celu, dziedziczenie obu z nich tworzy klasę z wieloma obowiązkami. Jednak to, czy jest to „zła rzecz”, zależy od charakteru odziedziczonych klas lub interfejsów.

Możesz podzielić swoje klasy i interfejsy na dwie główne grupy - te dotyczące zasadniczej złożoności systemu i te dotyczące jego przypadkowej złożoności. Jeśli dziedziczy po więcej niż jednej klasie „zasadniczej złożoności”, jest źle; jeśli odziedziczysz po jednej „niezbędnej” i jednej lub więcej „przypadkowych” klasach, to jest OK.

Na przykład w systemie rozliczeniowym możesz mieć klasy do reprezentowania faktur i cykli rozliczeniowych (dotyczą one zasadniczej złożoności) oraz klasy do utrwalania obiektów (dotyczą przypadkowej złożoności). Jeśli odziedziczysz w ten sposób

class BillingCycleInvoice : public BillingCycle, public Invoice {
};

to źle: ponosisz BillingCycleInvoicemieszaną odpowiedzialność, ponieważ dotyczy to zasadniczej złożoności systemu.

Z drugiej strony, jeśli odziedziczysz w ten sposób

class PersistentInvoice : public Invoice, public PersistentObject {
};

Twoja klasa jest w porządku: technicznie obsługuje dwa problemy naraz, ale ponieważ tylko jeden z nich jest niezbędny, możesz odpisać dziedziczenie przypadkowe jako „koszt prowadzenia działalności gospodarczej”.

dasblinkenlight
źródło
3
Twój drugi przykład jest bardzo podobny do tego, co byłoby przekrojowym problemem w programowaniu zorientowanym na aspekty. Oznacza to, że Twoje obiekty czasami muszą wykonywać czynności (takie jak rejestrowanie, testy kontroli dostępu lub utrwalanie), które nie są ich głównym celem, i że możesz to zrobić z wielokrotnym dziedziczeniem. To właściwe użycie tego narzędzia.
Najprościej jest, jeśli implementacja nowego interfejsu wymaga uzupełnienia funkcjonalności klasy, to nie jest. Ale jeśli zostanie
dodana
9

SRP jest wskazówką, aby unikać klas boskich, które są złe, ale można je również traktować zbyt dosłownie, a projekt rozpoczyna balon z mnóstwem klas, które naprawdę nie mogą nic zrobić bez połączenia garści. Wielokrotne dziedziczenie / wiele interfejsów może być znakiem, że klasa staje się zbyt duża, ale może to również oznaczać, że twoje skupienie jest zbyt szczegółowe dla danego zadania i twoje rozwiązanie jest przeprojektowane, lub może być po prostu idealnie ważne zastosowanie wielokrotnego dziedziczenia, a twoje obawy są bezpodstawne.

Projektowanie oprogramowania jest tak samo sztuką, jak nauką, stanowi równowagę wielu konkurujących ze sobą zainteresowań. Mamy zasady, które pomagają unikać rzeczy, o których wiemy, że są zbyt daleko w jednym kierunku, który powoduje problemy, ale zasady te mogą równie łatwo doprowadzić nas do miejsca, które jest tak samo złe lub gorsze, niż to, czego staraliśmy się uniknąć w pierwszej kolejności.

Ryathal
źródło
4

Nieco. Skupmy się na głównej zasadzie SRP:

Klasa powinna mieć tylko jeden powód do zmiany.

Wielokrotne dziedziczenie nie wymusza tego, ale prowadzi do tego. Jeśli klasa zastępuje lub implementuje swoje klasy podstawowe, jest to więcej powodów do zmiany. W ten sposób wielokrotne dziedziczenie interfejsu jest bardziej kłopotliwe niż dziedziczenie klas. Jeśli dwie klasy są dziedziczone, ale nie są zastępowane, wówczas powody zmiany istnieją w klasach podstawowych, a nie w nowej klasie.

Miejsca, w których wielokrotne dziedziczenie nie narusza SRP, to gdy wszystkie oprócz jednego z podstawowych typów nie są czymś, co klasa implementuje, ale działają jak cecha, którą ma klasa. Rozważ IDisposablew języku C #. Rzeczy, które są jednorazowe, nie mają dwóch obowiązków, interfejs działa tylko jako widok klas, które mają tę samą cechę, ale mają zdecydowanie różne obowiązki.

Telastyn
źródło
2

Moim zdaniem nie. Dla mnie jednym obowiązkiem jest „modelować użytkownika mojej aplikacji”. Być może będę musiał dostarczyć te dane za pośrednictwem usługi sieci Web i przechowywać je w relacyjnej bazie danych. Mógłbym zapewnić tę funkcjonalność, dziedzicząc kilka klas wtyczek.

To nie znaczy, że klasa ma trzy obowiązki. Oznacza to, że część odpowiedzialności za modelowanie użytkownika wymaga wsparcia serializacji i trwałości.

Kevin Cline
źródło
2

Ani trochę. W Javie możesz mieć interfejs reprezentujący jakiś obiekt domeny - na przykład Foo. Może być konieczne porównanie z innymi obiektami Foo, a zatem implementuje funkcję Porównywalną (z logiką porównawczą uzewnętrznioną w klasie Komparator); może się zdarzyć, że ten obiekt również musi być szeregowalny, aby można go było przenosić przez sieć; i może wymagać klonowania. Zatem klasa implementuje trzy interfejsy, ale nie ma żadnej logiki w celu ich obsługi poza obsługą Foo.

To powiedziawszy, głupotą jest doprowadzenie SRP do skrajności. Jeśli klasa definiuje więcej niż jedną metodę, czy narusza ona SRP? Wszystkie obiekty Java mają metodę toString () i metodę równości (Object), co oznacza, że ​​KAŻDY obiekt w Javie jest odpowiedzialny zarówno za równość, jak i autoprezentację. Czy to źle?

Matthew Flynn
źródło
1

Myślę, że to zależy od tego, jak zdefiniujesz odpowiedzialność. W klasycznym przykładzie iostream nie narusza SRP. IOstream odpowiada za zarządzanie jednym strumieniem dwukierunkowym.

Po wyczerpaniu się prymitywnych obowiązków przechodzisz na bardziej ogólne obowiązki na wysokim szczeblu, w końcu jak określasz odpowiedzialność za swój główny punkt wejścia? Jego zadaniem musi być „bycie głównym punktem wejścia”, a nie „wszystko, co robi moja aplikacja”, w przeciwnym razie nie można nie naruszyć SRP i stworzyć aplikacji.

kamieniste
źródło