Mam metodę, która tworzy plik danych po rozmowie z tablicą cyfrową:
CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)
Oto boardFileAccess
i boardMeasurer
ta sama instancja Board
obiektu, który implementuje zarówno IFileAccess
i IMeasurer
. IMeasurer
jest stosowany w tym przypadku do pojedynczej metody, która spowoduje, że jeden pin na płycie będzie aktywny, aby wykonać prosty pomiar. Dane z tego pomiaru są następnie przechowywane lokalnie na płycie za pomocą IFileAccess
. Board
znajduje się w osobnym projekcie.
Doszedłem do wniosku, że CreateDataFile
robienie jednej rzeczy polega na wykonaniu szybkiego pomiaru, a następnie zapisaniu danych, a wykonanie obu w tej samej metodzie jest bardziej intuicyjne dla kogoś innego, kto korzysta z tego kodu, niż konieczności wykonania pomiaru i zapisania do pliku jako osobne wywołania metod.
Wydaje mi się niewygodne dwukrotne przekazanie tego samego obiektu do metody. Zastanawiałem się nad stworzeniem lokalnego interfejsu, IDataFileCreator
który będzie rozszerzany, IFileAccess
a IMeasurer
następnie będzie miał implementację zawierającą Board
instancję, która po prostu wywoła wymagane Board
metody. Biorąc pod uwagę, że ten sam obiekt płytki byłby zawsze używany do pomiaru i zapisu plików, czy złym rozwiązaniem jest dwukrotne przekazywanie tego samego obiektu do metody? Jeśli tak, to czy używanie lokalnego interfejsu i implementacji jest odpowiednim rozwiązaniem?
źródło
Odpowiedzi:
Nie, w porządku. Oznacza to jedynie, że interfejs API jest nadmiernie zaprojektowany w odniesieniu do bieżącej aplikacji .
Ale to nie dowodzi, że nigdy nie będzie przypadku użycia, w którym źródło danych i miernik są różne. API ma na celu zaoferowanie możliwości programistom aplikacji, z których nie wszystkie zostaną wykorzystane. Nie powinieneś sztucznie ograniczać, co mogą zrobić użytkownicy interfejsu API, chyba że komplikuje to interfejs API, co obniża zrozumiałość sieci.
źródło
Zgadzam się z odpowiedzią @ KilianFoth, że jest w porządku.
Jeśli jednak chcesz, możesz utworzyć metodę, która pobiera pojedynczy obiekt, który implementuje oba interfejsy:
Nie ma ogólnego powodu, dla którego argumenty muszą być różnymi obiektami, a jeśli metoda wymagała różnych argumentów, byłby to szczególny wymóg w umowie, którą powinna wyjaśnić.
źródło
Myślę, że to właściwie twój problem. Metoda nie robi nic . Wykonuje dwie odrębne operacje, które dotyczą operacji we / wy na różnych urządzeniach , z których obie odciążają inne obiekty:
Są to dwie różne operacje we / wy. Warto zauważyć, że pierwszy z nich w żaden sposób nie powoduje mutacji systemu plików.
W rzeczywistości powinniśmy zauważyć, że istnieje domniemany środkowy krok:
Twój interfejs API powinien dostarczyć każdy z nich osobno w jakiejś formie. Skąd wiesz, że dzwoniący nie będzie chciał wykonać pomiaru bez przechowywania go w dowolnym miejscu? Skąd wiesz, że nie będą chcieli uzyskać pomiaru z innego źródła? Skąd wiesz, że nie będą chcieli przechowywać go w innym miejscu niż urządzenie? Istnieje dobry powód, aby oddzielić operacje. Na gołej minimum, każdy poszczególny element powinien być dostępny dla każdego obiektu wywołującego. Nie powinienem być zmuszany do zapisywania pomiaru do pliku, jeśli mój przypadek użycia tego nie wymaga.
Na przykład możesz oddzielić takie operacje.
IMeasurer
ma sposób na pobranie pomiaru:Twój typ pomiaru może być po prostu czymś prostym, na przykład a
string
lubdecimal
. Nie twierdzę, że potrzebujesz do tego interfejsu lub klasy, ale czyni to przykład bardziej ogólnym.IFileAccess
ma jakąś metodę zapisywania plików:Następnie potrzebujesz sposobu serializacji pomiaru. Zbuduj to w klasie lub interfejsie reprezentującym pomiar, lub zastosuj metodę użyteczności:
Nie jest jasne, czy ta operacja serializacji została jeszcze rozdzielona.
Ten rodzaj separacji poprawia interfejs API. Pozwala to osobie dzwoniącej decydować, czego potrzebują i kiedy, zamiast wymuszać z góry przyjęte pomysły na temat tego, co należy wykonać we / wy. Dzwoniący powinni mieć kontrolę nad wykonywaniem dowolnej prawidłowej operacji, niezależnie od tego, czy uważasz, że jest to przydatne, czy nie.
Gdy będziesz mieć osobne implementacje dla każdej operacji, twoja
CreateDataFile
metoda stanie się tylko skrótemWarto zauważyć, że po wykonaniu tego wszystkiego twoja metoda wnosi bardzo niewielką wartość. Powyższy wiersz kodu nie jest trudny dla twoich rozmówców do bezpośredniego użycia, a twoja metoda jest najwyżej dla wygody. Powinno być i jest czymś opcjonalnym . I to jest poprawny sposób działania API.
Po uwzględnieniu wszystkich istotnych części i stwierdzeniu, że metoda jest jedynie wygodą, musimy ponownie sformułować pytanie:
Jaki byłby najczęstszy przypadek użycia dla Twoich rozmówców?
Jeśli chodzi o to, aby typowy przypadek użycia pomiaru i pisania na tej samej płycie był nieco wygodniejszy, wówczas sensowne jest po prostu udostępnienie go
Board
bezpośrednio w klasie:Jeśli to nie poprawi wygody, nie zawracałbym sobie głowy tą metodą.
Ta metoda wygody rodzi jeszcze jedno pytanie.
Czy
IFileAccess
interfejs powinien wiedzieć o typie pomiaru i jak go serializować? Jeśli tak, możesz dodać metodę doIFileAccess
:Teraz dzwoniący po prostu zrób to:
który jest tak samo krótki i prawdopodobnie bardziej przejrzysty niż twoja metoda wygody przewidziana w pytaniu.
źródło
Klient konsumujący nie powinien mieć do czynienia z parą przedmiotów, gdy wystarczy jeden przedmiot. W twoim przypadku prawie tak nie jest, dopóki nie zostanie wywołane
CreateDataFile
.Potencjalnym rozwiązaniem, które sugerujesz, jest utworzenie połączonego interfejsu pochodnego. Takie podejście wymaga jednak pojedynczego obiektu, który implementuje oba interfejsy, co jest raczej ograniczające, prawdopodobnie nieszczelna abstrakcja, ponieważ jest zasadniczo dostosowane do konkretnej implementacji. Zastanów się, jak skomplikowane byłoby, gdyby ktoś chciał zaimplementować dwa interfejsy w osobnych obiektach: musiałby proxy wszystkich metod w jednym z interfejsów, aby przekazać je do drugiego obiektu. (FWIW, inną opcją jest po prostu scalenie interfejsów zamiast wymagania, aby jeden obiekt musiał zaimplementować dwa interfejsy za pośrednictwem interfejsu pochodnego.)
Jednak inne podejście, które mniej ogranicza / dyktuje implementację,
IFileAccess
jest połączone zIMeasurer
kompozycją, dzięki czemu jedno z nich jest powiązane z drugim i odnosi się do niego. (To nieco zwiększa abstrakcję jednego z nich, ponieważ teraz reprezentuje także parowanie.) NastępnieCreateDataFile
można wziąć tylko jeden z odnośników, powiedzmyIFileAccess
, i nadal uzyskać drugi w razie potrzeby. Twoja aktualna implementacja jako obiekt, który implementuje oba interfejsy po prostureturn this;
na odniesienie kompozycji, tutaj getter dlaIMeasurer
wIFileAccess
.Jeśli w pewnym momencie programowania parowanie okaże się fałszywe, co oznacza, że czasami używa się innego miernika z tym samym dostępem do pliku, możesz wykonać tę samą parowanie, ale na wyższym poziomie, co oznacza, że wprowadzony dodatkowy interfejs nie może być interfejsem pochodnym, ale interfejsem, który ma dwa moduły pobierające, łączące dostęp do pliku i miernik razem za pomocą kompozycji, a nie pochodnej. Klient konsumujący ma wówczas do czynienia z jednym przedmiotem, o ile trwa parowanie, oraz indywidualnymi przedmiotami, z którymi należy sobie poradzić (w celu skomponowania nowych par), gdy jest to konieczne.
Z drugiej strony mogę zapytać, kto jest właścicielem
CreateDataFile
, a pytanie dotyczy tego, kim jest ta strona trzecia. Mamy już klienta, który się wywołujeCreateDataFile
, obiekt / klasę będącą właścicielemCreateDataFile
orazIFileAccess
iIMeasurer
. Czasami, gdy przyjrzymy się szerszemu kontekstowi, mogą pojawić się alternatywne, czasem lepsze, organizacje. Trudno tu zrobić, ponieważ kontekst jest niekompletny, więc po prostu jedzenie do namysłu.źródło
Niektórzy wychowali się, że
CreateDataFile
robią za dużo. Mogę zasugerować, że zamiast tegoBoard
robi za dużo, ponieważ dostęp do pliku wydaje się być czymś innym niż reszta forum.Jeśli jednak założymy, że to nie pomyłka, większym problemem jest to, że interfejs powinien być zdefiniowany przez klienta, w tym przypadku
CreateDataFile
.Interfejs Segregacja Zasada mówi, że klient nie powinien polegać na bardziej interfejs niż to, czego potrzebuje. Pożyczając zdanie z tej drugiej odpowiedzi , można to sparafrazować jako „interfejs jest zdefiniowany przez potrzeby klienta”.
Teraz możliwe jest skomponowanie interfejsu specyficznego dla klienta za pomocą
IFileAccess
i,IMeasurer
jak sugerują inne odpowiedzi, ale ostatecznie klient ten powinien mieć interfejs specjalnie dla niego dostosowany.źródło