Mam moduł, powiedz „M”, który ma kilku klientów, powiedz „C1”, „C2”, „C3”. Chcę podzielić przestrzeń nazw modułu M, tj. Deklaracje interfejsów API i danych, które udostępnia, do plików nagłówkowych w taki sposób, aby -
- dla każdego klienta widoczne są tylko wymagane przez niego dane i interfejsy API; reszta przestrzeni nazw modułu jest ukryta przed klientem, tj. przestrzegaj zasady segregacji interfejsu .
- deklaracja nie jest powtarzana w wielu plikach nagłówkowych, tj. nie narusza DRY .
- moduł M nie ma żadnych zależności od swoich klientów.
- na klienta nie mają wpływu zmiany dokonane w częściach modułu M, które nie są przez niego używane.
- na istniejących klientów nie ma wpływu dodanie (lub usunięcie) większej liczby klientów.
Obecnie radzę sobie z tym, dzieląc przestrzeń nazw modułu w zależności od wymagań jego klientów. Na przykład na poniższym obrazku pokazano różne części przestrzeni nazw modułu wymaganej przez 3 klientów. Wymagania klienta pokrywają się. Przestrzeń nazw modułu podzielona jest na 4 osobne pliki nagłówkowe - „a”, „1”, „2” i „3” .
Jednak narusza to niektóre z wyżej wymienionych wymagań, tj. R3 i R5. Wymaganie 3 zostało naruszone, ponieważ partycjonowanie zależy od charakteru klientów; także po dodaniu nowego klienta podział ten zmienia się i narusza wymaganie 5. Jak widać po prawej stronie powyższego obrazu, przestrzeń robocza modułu jest teraz podzielona na 7 plików nagłówkowych po dodaniu nowego klienta - „a ”,„ b ”,„ c ”,„ 1 ”,„ 2 * ”,„ 3 * ”i„ 4 ” . Pliki nagłówkowe przeznaczone dla 2 starszych zmian klientów, powodując w ten sposób ich odbudowę.
Czy istnieje sposób na osiągnięcie segregacji interfejsu w C w nieskomplikowany sposób?
Jeśli tak, jak poradziłbyś sobie z powyższym przykładem?
Nierealne hipotetyczne rozwiązanie, które, jak sobie wyobrażam, byłoby -
Moduł ma 1 gruby plik nagłówka obejmujący całą przestrzeń nazw. Ten plik nagłówka jest podzielony na adresowalne sekcje i podsekcje, takie jak strona Wikipedii. Każdy klient ma następnie dostosowany do niego określony plik nagłówka. Pliki nagłówkowe specyficzne dla klienta to tylko lista hiperłączy do sekcji / podsekcji grubego pliku nagłówkowego. System kompilacji musi rozpoznać specyficzny dla klienta plik nagłówka jako „zmodyfikowany”, jeśli jakakolwiek sekcja wskazana w nagłówku modułu jest zmodyfikowana.
źródło
struct
jest to, czego używasz w C, gdy potrzebujesz interfejsu. To prawda, że metody są trochę trudne. Może Cię to zainteresować: cs.rit.edu/~ats/books/ooc.pdfstruct
ifunction pointers
.Odpowiedzi:
Segregacja interfejsu zasadniczo nie powinna opierać się na wymaganiach klienta. Powinieneś zmienić całe podejście, aby to osiągnąć. Powiedziałbym, że można zmodularyzować interfejs, grupując funkcje w spójne grupy. To znaczy, że grupowanie opiera się na spójności samych funkcji, a nie na wymaganiach klienta. W takim przypadku będziesz mieć zestaw interfejsów, I1, I2, ... itd. Klient C1 może używać samego I2. Klient C2 może korzystać z I1 i I5 itd. Pamiętaj, że jeśli klient używa więcej niż jednego Ii, nie stanowi problemu. Jeśli rozłożyłeś interfejs na spójne moduły, to właśnie w tym tkwi sedno sprawy.
Ponownie, ISP nie jest oparty na kliencie. Chodzi o rozkład interfejsu na mniejsze moduły. Jeśli zostanie to zrobione poprawnie, zapewni również, że klienci będą narażeni na tak mało funkcji, jak potrzebują.
Dzięki takiemu podejściu Twoi klienci mogą powiększać się do dowolnej liczby, ale nie wpływa to na Ciebie. Każdy klient użyje jednej lub kilku kombinacji interfejsów w zależności od potrzeb. Czy zdarzają się przypadki, że klient C musi dołączyć powiedzmy I1 i I3, ale nie będzie korzystać ze wszystkich funkcji tych interfejsów? Tak, to nie jest problem. Po prostu używa najmniejszej liczby interfejsów.
źródło
Interfejs Segregacja Zasada mówi:
Tutaj jest kilka pytań bez odpowiedzi. Jeden jest:
Jaki mały?
Mówisz:
Nazywam to ręcznym pisaniem kaczek . Budujesz interfejsy, które ujawniają tylko to, czego potrzebuje klient. Zasadą segregacji interfejsów nie jest po prostu ręczne pisanie kaczek.
Ale dostawca usług internetowych to nie tylko wezwanie do stworzenia „spójnych” interfejsów ról, które można ponownie wykorzystać. Żaden „spójny” interfejs interfejsu roli nie może doskonale zabezpieczyć się przed dodaniem nowego klienta z własnymi potrzebami.
ISP to sposób na odizolowanie klientów od wpływu zmian w usłudze. Jego celem było przyspieszenie kompilacji podczas wprowadzania zmian. Pewnie, że ma inne zalety, takie jak nie łamanie klientów, ale to był główny punkt. Jeśli zmieniam
count()
podpis funkcji usług , dobrze jest, jeśli nieużywani kliencicount()
nie muszą być edytowani i rekompilowani.DLACZEGO zależy mi na zasadzie segregacji interfejsu. Nie uważam tego za tak ważne. Rozwiązuje prawdziwy problem.
Sposób, w jaki powinien być zastosowany, powinien rozwiązać problem. Nie ma rutynowego sposobu na zastosowanie ISP, którego nie można pokonać tylko odpowiednim przykładem potrzebnej zmiany. Powinieneś przyjrzeć się zmianom systemu i dokonać wyborów, które pozwolą ci się uspokoić. Sprawdźmy opcje.
Najpierw zadaj sobie pytanie: czy utrudnianie obecnie zmian w interfejsie usługi? Jeśli nie, wyjdź na zewnątrz i baw się, aż się uspokoisz. To nie jest ćwiczenie intelektualne. Upewnij się, że lekarstwo nie jest gorsze niż choroba.
Jeśli wielu klientów korzysta z tego samego podzbioru funkcji, przemawia to za „spójnymi” interfejsami wielokrotnego użytku. Podzbiór prawdopodobnie skupia się na jednym pomyśle, który możemy uznać za rolę, jaką usługa zapewnia klientowi. Miło, gdy to działa. To nie zawsze działa.
Jeśli wielu klientów korzysta z różnych podzbiorów funkcji, możliwe, że klient faktycznie korzysta z usługi przez wiele ról. Zgadza się, ale utrudnia dostrzeżenie ról. Znajdź ich i spróbuj się z nimi drażnić. To może nas z powrotem przenieść na przypadek 1. Klient po prostu korzysta z usługi za pośrednictwem więcej niż jednego interfejsu. Nie zaczynaj przesyłania usługi. Jeśli cokolwiek oznaczałoby to przekazanie usługi klientowi więcej niż jeden raz. To działa, ale zastanawiam się, czy usługa nie jest wielką kulą błota, którą należy rozbić.
Jeśli wielu klientów korzysta z różnych podzbiorów, ale nie widzisz ról, nawet pozwalając klientom korzystać z więcej niż jednego, to nie masz nic lepszego niż pisanie w klawiaturze do projektowania interfejsów. Ten sposób projektowania interfejsów zapewnia, że klient nie jest narażony na działanie nawet jednej funkcji, z której nie korzysta, ale prawie gwarantuje, że dodanie nowego klienta zawsze będzie wymagało dodania nowego interfejsu, którego wdrożenie usługi nie musi wiedzieć o tym będzie interfejs, który agreguje interfejsy ról. Po prostu wymieniliśmy jeden ból na inny.
Jeśli wielu klientów korzysta z różnych podzbiorów, nakładają się, oczekuje się, że zostaną dodani nowi klienci, którzy będą potrzebować nieprzewidzianych podzbiorów, a Ty nie chcesz przerywać usługi, a następnie rozważ bardziej funkcjonalne rozwiązanie. Ponieważ dwie pierwsze opcje nie zadziałały i naprawdę jesteś w złym miejscu, w którym nic nie jest zgodne z wzorcem i nadchodzą kolejne zmiany, zastanów się nad udostępnieniem każdej funkcji własnego interfejsu. Skończenie tutaj nie oznacza, że ISP zawiódł. Jeśli coś zawiodło, był to obiektowy paradygmat. Interfejsy z pojedynczą metodą w skrajnym przypadku podążają za ISP. To trochę pisanie na klawiaturze, ale może się nagle okazać, że interfejsy mogą być ponownie używane. Ponownie upewnij się, że nie ma
Okazuje się, że mogą stać się bardzo małe.
Podjąłem to pytanie jako wyzwanie do zastosowania ISP w najbardziej ekstremalnych przypadkach. Pamiętaj jednak, że najlepiej unikać skrajności. W dobrze przemyślanym projekcie stosującym inne zasady SOLID te problemy zwykle nie występują ani nie mają znaczenia, prawie w takim samym stopniu.
Kolejne pytanie bez odpowiedzi:
Kto jest właścicielem tych interfejsów?
W kółko widzę interfejsy zaprojektowane zgodnie z czymś, co nazywam mentalnością „biblioteki”. Wszyscy jesteśmy winni kodowania małpa-patrz-małpa-do, w którym po prostu coś robisz, ponieważ tak właśnie to widziałeś. To samo jesteśmy winni interfejsom.
Kiedy patrzę na interfejs zaprojektowany dla klasy w bibliotece, pomyślałem: o, ci faceci są zawodowcami. To musi być właściwy sposób wykonania interfejsu. Nie rozumiałem, że granica biblioteki ma swoje potrzeby i problemy. Po pierwsze, biblioteka całkowicie nie zna projektu swoich klientów. Nie każda granica jest taka sama. A czasami nawet ta sama granica ma różne sposoby jej przekroczenia.
Oto dwa proste sposoby spojrzenia na projekt interfejsu:
Interfejs serwisowy. Niektórzy ludzie projektują każdy interfejs, aby pokazać wszystko, co usługa może zrobić. Możesz nawet znaleźć opcje refaktoryzacji w IDE, które napiszą dla ciebie interfejs, używając dowolnej klasy, którą go karmisz.
Interfejs należący do klienta. Wydaje się, że ISP twierdzi, że jest to słuszne, a własność usługi jest błędna. Powinieneś rozbić każdy interfejs z myślą o potrzebach klientów. Ponieważ klient jest właścicielem interfejsu, powinien go zdefiniować.
Więc kto ma rację?
Rozważ wtyczki:
Kto jest właścicielem interfejsów tutaj? Klienci? Usługi?
Okazuje się jedno i drugie.
Kolory tutaj są warstwami. Czerwona warstwa (po prawej) nie powinna nic wiedzieć o zielonej warstwie (po lewej). Zieloną warstwę można zmienić lub zastąpić bez dotykania czerwonej warstwy. W ten sposób każda zielona warstwa może zostać podłączona do czerwonej warstwy.
Lubię wiedzieć, co powinno wiedzieć o czym, a czego nie powinno wiedzieć. Dla mnie „co wie o czym?” To najważniejsze pytanie architektoniczne.
Wyjaśnijmy trochę słownictwa:
Klient jest czymś, co wykorzystuje.
Usługa jest czymś, co jest używane.
Interactor
tak się składa, że jest to jedno i drugie.ISP twierdzi, że zrywają interfejsy dla klientów. Dobrze, zastosujmy to tutaj:
Presenter
(usługa) nie powinna dyktowaćOutput Port <I>
interfejsu. Interfejs powinien zostać zawężony doInteractor
potrzeb (tutaj działających jako klient). Oznacza to, że interfejs WIE oInteractor
i, aby podążać za ISP, musi się z nim zmienić. I w porządku.Interactor
(tutaj działający jako usługa) nie powinien dyktowaćInput Port <I>
interfejsu. Interfejs powinien zostać zawężony doController
potrzeb (klienta). Oznacza to, że interfejs WIE oController
i, aby podążać za ISP, musi się z nim zmienić. I to nie jest w porządku.Drugi nie jest w porządku, ponieważ czerwona warstwa nie powinna wiedzieć o zielonej warstwie. Czy zatem dostawca usług internetowych się myli? A więc, coś w tym stylu, tak. Żadna zasada nie jest absolutna. Jest to przypadek, w którym głupcy, którzy lubią interfejs, aby pokazać wszystko, co usługa może zrobić, okazują się słuszni.
Przynajmniej mają rację, jeśli
Interactor
nie robi nic poza potrzebami tego przypadku użycia. JeśliInteractor
robi to dla innych przypadków użycia, nie ma powoduInput Port <I>
, aby to wiedzieć. Nie jestem pewien, dlaczegoInteractor
nie można skupić się tylko na jednym przypadku użycia, więc nie stanowi to problemu, ale coś się dzieje.Ale
input port <I>
interfejs po prostu nie może podporządkować sięController
klientowi i sprawić, że będzie to prawdziwa wtyczka. To granica „biblioteki”. Zupełnie inny sklep programistyczny mógłby pisać zieloną warstwę wiele lat po opublikowaniu czerwonej warstwy.Jeśli przekraczasz granicę „biblioteki” i odczuwasz potrzebę zastosowania ISP, nawet jeśli nie jesteś właścicielem interfejsu po drugiej stronie, będziesz musiał znaleźć sposób na zawężenie interfejsu bez jego zmiany.
Jednym ze sposobów na wyciągnięcie tego jest adapter. Umieść go między klientami podobnymi
Controler
aInput Port <I>
interfejsem. Adapter akceptujeInteractor
jakoInput Port <I>
i przekazuje mu swoją pracę. Odsłania jednak tylko to, czegoController
potrzebują klienci za pośrednictwem interfejsu roli lub interfejsów należących do zielonej warstwy. Adapter sam nie podąża za ISP, ale umożliwia bardziej złożoną klasę, taką jakController
korzystanie z ISP. Jest to przydatne, jeśli jest mniej adapterów niż klienciController
używający ich, a gdy znajdujesz się w nietypowej sytuacji, gdy przekraczasz granicę biblioteki i pomimo opublikowania biblioteka nie przestaje się zmieniać. Patrzę na ciebie Firefox. Teraz te zmiany tylko psują twoje adaptery.Co to znaczy? Oznacza to, że szczerze mówiąc, nie dostarczyłeś mi wystarczających informacji, aby powiedzieć ci, co powinieneś zrobić. Nie wiem, czy niestosowanie się do usługodawcy internetowego powoduje problem. Nie wiem, czy przestrzeganie go nie spowoduje więcej problemów.
Wiem, że szukasz prostej zasady przewodniej. ISP stara się być tym. Ale pozostawia wiele niedopowiedzeń. Wierzę w to. Tak, nie zmuszaj klientów do polegania na metodach, których nie używają, bez ważnego powodu!
Jeśli masz dobry powód, np. Projektujesz coś, co akceptuje wtyczki, pamiętaj o problemach, które nie podążają za przyczynami dostawcy usług internetowych (trudno je zmienić bez łamania klientów) i sposobach na ich złagodzenie (utrzymaj
Interactor
lub przynajmniejInput Port <I>
skoncentruj się na jednym stabilnym przypadek użycia).źródło
Więc ten punkt:
Zrezygnuj z tego, że naruszasz inną ważną zasadę, jaką jest YAGNI. Zależy mi na tym, gdy mam setki klientów. Myślenie o czymś z góry, a wtedy okaże się, że nie masz żadnych dodatkowych klientów dla tego kodu, jest lepsze niż cel.
druga
Dlaczego twój kod nie używa DI, inwersji zależności, nic, nic w bibliotece nie powinno zależeć od natury twojego klienta.
W końcu wygląda na to, że potrzebujesz dodatkowej warstwy pod swoim kodem, aby zaspokoić potrzeby nakładania się rzeczy (DI, więc twój kod frontowy zależy tylko od tej dodatkowej warstwy, a klienci zależą tylko od interfejsu frontowego) w ten sposób pokonujesz DRY.
Naprawdę byś to zrobił. Więc robisz te same rzeczy, których używasz w warstwie modułów poniżej innego modułu. W ten sposób, mając warstwę poniżej, osiągniesz:
tak
tak
tak
tak
źródło
Te same informacje, które podano w deklaracji, są zawsze powtarzane w definicji. Tak właśnie działa ten język. Ponadto, powtarzając deklarację w wielu plikach nagłówkowych nie narusza DRY . Jest to dość powszechnie stosowana technika (przynajmniej w standardowej bibliotece).
Powtórzenie dokumentacji lub implementacji naruszy DRY .
Nie zawracałbym sobie tym głowy, chyba że kod klienta nie jest napisany przeze mnie.
źródło
Wykluczam moje zamieszanie. Jednak twój praktyczny przykład rysuje rozwiązanie w mojej głowie. Jeśli potrafię wyrazić własnymi słowami: wszystkie partycje w module
M
mają wiele do wielu wyłącznych relacji z dowolnym klientem.Przykładowa struktura
Mh
Mc
W pliku Mc tak naprawdę nie musisz używać #ifdefs, ponieważ to, co umieścisz w pliku .c, nie wpływa na pliki klienta, o ile zdefiniowane są funkcje plików klienta.
C1.c
C2.c
C3.c
Znów nie jestem pewien, czy o to pytasz. Więc weź to z odrobiną soli.
źródło
P1_init()
iP2_init()
?P1_init()
i coP2_init()
łączy?_PREF_
to, co zostało ostatnio zdefiniowane. Tak_PREF_init()
będzie zP1_init()
powodu ostatniej instrukcji #define. Następnie następna instrukcja definiuje PREF równą P2_, generując w ten sposóbP2_init()
.