Zacząłem używać zasady projektowania i korzystania z interfejsów, która mówi w zasadzie: „proś tylko o to, czego potrzebujesz”.
Na przykład, jeśli mam kilka typów, które można usunąć, utworzę Deletable
interfejs:
interface Deletable {
void delete();
}
Następnie mogę napisać ogólną klasę:
class Deleter<T extends Deletable> {
void delete(T t) {
t.delete();
}
}
W innym miejscu kodu zawsze poproszę o jak najmniejszą odpowiedzialność za spełnienie potrzeb kodu klienta. Więc jeśli muszę tylko usunąć File
, nadal będę prosić o, a Deletable
nie o File
.
Czy ta zasada jest powszechnie znana i ma już przyjętą nazwę? Czy to kontrowersyjne? Czy jest to omówione w podręcznikach?
object-oriented
interfaces
solid
single-responsibility
glenviewjeff
źródło
źródło
Odpowiedzi:
Uważam, że odnosi się to do tego, co Robert Martin nazywa zasadą segregacji interfejsów . Interfejsy są podzielone na małe i zwięzłe, aby konsumenci (klienci) musieli wiedzieć tylko o interesujących ich metodach. Możesz sprawdzić więcej na SOLID .
źródło
Aby rozwinąć bardzo dobrą odpowiedź Vadima, odpowiem na pytanie „czy to kontrowersyjne” pytaniem „nie, nie bardzo”.
Ogólnie rzecz biorąc, segregacja interfejsów jest dobra, ponieważ zmniejsza ogólną liczbę „powodów zmiany” różnych zaangażowanych obiektów. Podstawową zasadą jest to, że gdy interfejs z wieloma metodami musi zostać zmieniony, powiedzmy, aby dodać parametr do jednej z metod interfejsu, wówczas wszyscy odbiorcy interfejsu muszą przynajmniej zostać ponownie skompilowani, nawet jeśli nie użyli zmienionej metody. „Ale to tylko rekompilacja!”, Słyszę, jak mówisz; może to być prawda, ale pamiętaj, że zwykle wszystko, co rekompilujesz, musi zostać wypchnięte jako część poprawki oprogramowania, bez względu na to, jak znacząca jest zmiana w pliku binarnym. Reguły te zostały pierwotnie opracowane na początku lat 90., gdy średnia komputerowa stacja robocza była mniej wydajna niż telefon w kieszeni, płonęło połączenie modemowe o szybkości 14,4 kb, a 3,5 „1,44 MB” dyskietek było głównym nośnikiem wymiennym. Nawet w obecnej erze 3G / 4G użytkownicy Internetu bezprzewodowego często mają plany transmisji danych z ograniczeniami, więc po wydaniu aktualizacji im mniej plików binarnych, które trzeba pobrać, tym lepiej.
Jednak, podobnie jak wszystkie dobre pomysły, segregacja interfejsu może się pogorszyć, jeśli zostanie niewłaściwie wdrożona. Po pierwsze, istnieje możliwość, że segregując interfejsy, utrzymując obiekt, który implementuje te interfejsy (spełniając zależności), względnie niezmieniony, możesz otrzymać „Hydrę”, krewnego anty-wzorca „Boski Obiekt”, w którym wszechwiedząca, wszechpotężna natura obiektu jest ukryta przed zależnymi od wąskich interfejsów. Skończysz z wielogłowym potworem, który jest co najmniej tak trudny do utrzymania, jak Boski Obiekt, plus koszty utrzymania wszystkich jego interfejsów. Nie ma dużej liczby interfejsów, których nie powinieneś przekraczać, ale każdy interfejs zaimplementowany w jednym obiekcie powinien być poprzedzony odpowiedzią na pytanie: „Czy ten interfejs przyczynia się do obiektu”
Po drugie, interfejs dla każdej metody może nie być konieczny, pomimo informacji SRP. Możesz skończyć z „kodem ravioli”; tak wiele kawałków wielkości kęsa, że trudno jest prześledzić, aby dowiedzieć się, gdzie dokładnie się dzieje. Nie jest również konieczne dzielenie interfejsu na dwie metody, jeśli wszyscy obecni użytkownicy tego interfejsu potrzebują obu metod. Nawet jeśli jedna z klas zależnych potrzebuje tylko jednej z dwóch metod, ogólnie dopuszczalne jest, aby nie dzielić interfejsu, jeśli jego metody koncepcyjnie mają bardzo wysoką spójność (dobre przykłady to „metody antonimiczne”, które są ze sobą dokładnie przeciwieństwami).
Segregacja interfejsu powinna opierać się na klasach zależnych od interfejsu:
Jeśli od interfejsu zależy tylko jedna klasa, nie segreguj. Jeśli klasa nie używa jednej lub więcej metod interfejsu i jest to jedyny konsument interfejsu, istnieje duże prawdopodobieństwo, że nie powinieneś narażać tych metod w pierwszej kolejności.
Jeśli istnieje więcej niż jedna klasa zależna od interfejsu, a wszystkie osoby zależne używają wszystkich metod interfejsu, nie segreguj; jeśli musisz zmienić interfejs (w celu dodania metody lub zmiany podpisu), wszyscy obecni konsumenci zostaną dotknięci zmianą niezależnie od tego, czy segregujesz, czy nie (chociaż jeśli dodajesz metodę, której co najmniej jedna osoba zależna nie będzie potrzebować, rozważ ostrożnie, jeśli zmiana powinna zostać zaimplementowana jako nowy interfejs, być może dziedziczący po istniejącym).
Jeśli istnieje więcej niż jedna klasa zależna od interfejsu i nie używają one tych samych metod, jest to kandydat do segregacji. Spójrz na „spójność” interfejsu; czy wszystkie metody wspierają jeden, bardzo konkretny cel programowania? Jeśli możesz zidentyfikować więcej niż jeden główny cel interfejsu (i jego implementatorów), rozważ podzielenie interfejsów wzdłuż tych linii, aby utworzyć mniejsze interfejsy z mniejszą liczbą „powodów do zmiany”.
źródło
IBaz : IFoo, IBar
i wymaganie tego.IFoo
iIBar
, zdefiniowanie kompozytuIFooBar
może być dobrym pomysłem, ale jeśli interfejsy są dokładnie podzielone, łatwo jest uzyskać dziesiątki różnych typów interfejsów. Weź pod uwagę następujące funkcje kolekcji: Wylicz, zlicz liczbę, odczytaj n-ty element, napisz n-ty element, wstaw przed n-tym elementem, usuń n-ty element, nowy element (powiększ kolekcję i zwróć indeks nowej przestrzeni) i dodaj. Dziewięć metod: ECRWIDNA. Prawdopodobnie mógłbym opisać dziesiątki typów, które naturalnie wspierałyby wiele różnych kombinacji.