Co jest ogólnie przyjętą praktyką między tymi dwoma przypadkami:
function insertIntoDatabase(Account account, Otherthing thing) {
database.insertMethod(account.getId(), thing.getId(), thing.getSomeValue());
}
lub
function insertIntoDatabase(long accountId, long thingId, double someValue) {
database.insertMethod(accountId, thingId, someValue);
}
Innymi słowy, czy ogólnie lepiej jest przekazywać całe obiekty, czy tylko pola, których potrzebujesz?
Odpowiedzi:
Żadna z nich nie jest ogólnie lepsza od drugiej. Jest to wezwanie do osądu, które należy wykonać indywidualnie dla każdego przypadku.
Ale w praktyce, kiedy jesteś w stanie podjąć tę decyzję, to dlatego, że możesz zdecydować, która warstwa w ogólnej architekturze programu powinna rozbijać obiekt na prymitywy, więc powinieneś pomyśleć o całym wywołaniu stosu , a nie tylko tej jednej metody, w której się obecnie znajdujesz. Prawdopodobnie zerwanie musi być gdzieś zrobione i nie miałoby sensu (lub byłoby niepotrzebnie podatne na błędy), aby zrobić to więcej niż jeden raz. Pytanie brzmi, gdzie powinno być to jedno miejsce.
Najłatwiejszym sposobem podjęcia tej decyzji jest zastanowienie się, jaki kod powinien lub nie powinien zostać zmieniony, jeśli obiekt zostanie zmieniony . Rozwińmy nieco twój przykład:
vs
W pierwszej wersji kod interfejsu użytkownika ślepo przekazuje
data
obiekt i to do kodu bazy danych można wyodrębnić z niego przydatne pola. W drugiej wersji kod interfejsu użytkownika dzielidata
obiekt na przydatne pola, a kod bazy danych odbiera je bezpośrednio, nie wiedząc, skąd pochodzą. Kluczową implikacją jest to, że jeśli strukturadata
obiektu miałaby się w jakiś sposób zmienić, pierwsza wersja wymagałaby zmiany tylko kodu bazy danych, podczas gdy druga wersja wymagałaby zmiany tylko kodu interfejsu użytkownika . To, która z tych dwóch wartości jest poprawna, zależy w dużej mierze od tego, jakie danedata
zawiera obiekt, ale zwykle jest to bardzo oczywiste. Na przykład jeślidata
jest ciągiem dostarczonym przez użytkownika, takim jak „20/05/1999”, powinno być do kodu interfejsu użytkownika, aby przekonwertować go na odpowiedniDate
typ przed przekazaniem.źródło
Nie jest to wyczerpująca lista, ale weź pod uwagę niektóre z następujących czynników przy podejmowaniu decyzji, czy obiekt powinien zostać przekazany do metody jako argument:
Czy obiekt jest niezmienny? Czy funkcja jest „czysta”?
Skutki uboczne są ważną kwestią do rozważenia przy utrzymywaniu kodu. Gdy widzisz kod z wieloma zmiennymi obiektami stanowymi przesyłanymi w dowolnym miejscu, kod ten jest często mniej intuicyjny (w taki sam sposób, jak globalne zmienne stanu często mogą być mniej intuicyjne), a debugowanie często staje się trudniejsze i czasochłonne trawiący.
Zasadniczo staraj się zapewnić, tak dalece, jak jest to racjonalnie możliwe, że wszelkie obiekty, które przekazujesz metodzie, są wyraźnie niezmienne.
Unikaj (ponownie, o ile jest to racjonalnie możliwe) jakiegokolwiek projektu, w którym oczekuje się zmiany stanu argumentu w wyniku wywołania funkcji - jednym z najsilniejszych argumentów za tym podejściem jest zasada najmniejszego zdziwienia ; tzn. ktoś czytający twój kod i widziący argument przekazany do funkcji „mniej prawdopodobne” spodziewa się zmiany jej stanu po powrocie funkcji.
Ile argumentów ma już metoda?
Metody z nadmiernie długimi listami argumentów (nawet jeśli większość z tych argumentów ma wartości „domyślne”) zaczynają wyglądać jak zapach kodu. Czasami takie funkcje są jednak konieczne i możesz rozważyć utworzenie klasy, której jedynym celem jest działanie jak obiekt parametru .
Takie podejście może wymagać niewielkiej ilości dodatkowego mapowania kodu typu „kocioł” z obiektu „źródłowego” na obiekt parametru, ale jest to dość niski koszt zarówno pod względem wydajności, jak i złożoności, a ponadto istnieje szereg korzyści w zakresie oddzielenia i niezmienność obiektu.
Czy przekazywany obiekt należy wyłącznie do „warstwy” w aplikacji (na przykład ViewModel lub jednostka ORM?)
Pomyśl o separacji problemów (SoC) . Czasami zadając sobie pytanie, czy obiekt „należy” do tej samej warstwy lub modułu, w którym istnieje twoja metoda (np. Ręcznie zwijana biblioteka otoki API lub podstawowa warstwa logiki biznesowej itp.), Może poinformować, czy obiekt ten naprawdę powinien zostać przekazany do tego metoda.
SoC jest dobrym fundamentem do pisania czystego, luźno sprzężonego, modułowego kodu. na przykład idealnie nie należy przekazywać obiektu encji ORM (mapowanie między kodem a schematem bazy danych) w warstwie biznesowej lub, co gorsza, w warstwie prezentacji / interfejsu użytkownika.
W przypadku przekazywania danych między „warstwami” zwykle przekazanie do metody parametrów zwykłych danych jest zwykle lepsze niż przekazanie obiektu z „niewłaściwej” warstwy. Chociaż prawdopodobnie dobrym pomysłem jest posiadanie osobnych modeli, które istnieją na „właściwej” warstwie, na którą można zamiast tego zmapować mapę.
Czy sama funkcja jest po prostu zbyt duża i / lub złożona?
Gdy funkcja potrzebuje wielu elementów danych, warto zastanowić się, czy ta funkcja przyjmuje zbyt wiele obowiązków; poszukaj potencjalnych możliwości refaktoryzacji przy użyciu mniejszych obiektów i krótszych, prostszych funkcji.
Czy funkcja powinna być obiektem polecenia / zapytania?
W niektórych przypadkach związek między danymi a funkcją może być bliski; w takich przypadkach zastanów się, czy odpowiedni byłby obiekt polecenia lub obiekt zapytania .
Czy dodanie parametru obiektu do metody zmusza zawierającą klasę do przyjęcia nowych zależności?
Czasami najsilniejszym argumentem dla argumentów „Zwykłe stare dane” jest po prostu to, że klasa odbierająca jest już starannie zamknięta, a dodanie parametru obiektu do jednej z jej metod zanieczyściłoby klasę (lub jeśli klasa jest już zanieczyszczona, wtedy będzie pogorszyć istniejącą entropię)
Czy naprawdę potrzebujesz ominąć cały obiekt, czy potrzebujesz tylko niewielkiej części interfejsu tego obiektu?
Zastanów się nad zasadą segregacji interfejsu w odniesieniu do twoich funkcji - tzn. Kiedy przekazujesz obiekt, powinno ono zależeć tylko od części interfejsu tego argumentu, którego on (funkcja) faktycznie potrzebuje.
źródło
Kiedy tworzysz funkcję, domyślnie deklarujesz jakąś umowę z kodem, który ją wywołuje. „Ta funkcja pobiera te informacje i zamienia je w inną rzecz (prawdopodobnie z efektami ubocznymi)”.
Czy więc umowa powinna logicznie dotyczyć obiektów (niezależnie od tego, jak są one zaimplementowane), czy też pól, które akurat są częścią tych innych obiektów. W każdym razie dodajesz sprzężenie, ale jako programista musisz zdecydować, do kogo należy.
Ogólnie rzecz biorąc , jeśli jest niejasne, faworyzuj najmniejsze dane niezbędne do działania funkcji. Często oznacza to przekazywanie tylko pól, ponieważ funkcja nie potrzebuje innych rzeczy znalezionych w obiektach. Ale czasami przyjęcie całego obiektu jest bardziej poprawne, ponieważ powoduje mniejszy wpływ, gdy rzeczy nieuchronnie się zmienią w przyszłości.
źródło
To zależy.
Aby rozwinąć, parametry, które akceptuje twoja metoda, powinny semantycznie odpowiadać temu, co próbujesz zrobić. Rozważ
EmailInviter
trzy możliwe implementacjeinvite
metody:Przekazywanie miejsca, w
String
którym powinieneś przekazać,EmailAddress
jest wadliwe, ponieważ nie wszystkie ciągi znaków są adresami e-mail.EmailAddress
Klasa lepiej semantycznie odpowiada za zachowanie metody. Jednak przekazywanieUser
jest również wadliwe, ponieważ dlaczego, u licha, powinno sięEmailInviter
ograniczać do zapraszania użytkowników? Co z firmami? Co jeśli czytasz adresy e-mail z pliku lub wiersza poleceń i nie są one powiązane z użytkownikami? Listy mailingowe? I tak dalej.Istnieje kilka znaków ostrzegawczych, których można użyć jako wskazówek. Jeśli używasz prostego jak typ wartości
String
lubint
ale nie wszystkie ciągi lub ints są ważne czy jest coś „specjalne” o nich, należy używać bardziej sensowne typu. Jeśli używasz obiektu, a jedyne, co robisz, to wywoływanie gettera, powinieneś zamiast tego przekazać obiekt bezpośrednio do gettera. Te wytyczne nie są ani twarde, ani szybkie, ale niewiele jest.źródło
Clean Code zaleca posiadanie jak najmniejszej liczby argumentów, co oznacza, że Object byłby zwykle lepszym podejściem i myślę, że ma to jakiś sens. dlatego
jest bardziej czytelnym połączeniem niż
źródło
Omiń obiekt, a nie jego stan składowy. Jest to zgodne z obiektowymi zasadami enkapsulacji i ukrywania danych. Ujawnienie wnętrzności obiektu w różnych interfejsach metod, w których nie jest to konieczne, narusza podstawowe zasady OOP.
Co się stanie, jeśli zmienisz pola w
Otherthing
? Może zmienisz typ, dodasz pole lub usuniesz pole. Teraz wszystkie metody, takie jak te wymienione w pytaniu, muszą zostać zaktualizowane. Jeśli ominiesz obiekt, nie będzie żadnych zmian interfejsu.Jedynym momentem, w którym powinieneś napisać metodę akceptującą pola na obiekcie, jest napisanie metody pobierania obiektu:
W momencie wykonywania tego wywołania kod wywołujący nie ma jeszcze odwołania do obiektu, ponieważ celem wywołania tej metody jest uzyskanie obiektu.
źródło
Otherthing
?” (1) Byłoby to naruszeniem zasady otwartej / zamkniętej. (2) nawet jeśli przekażesz cały obiekt, kod w nim, a następnie uzyska dostęp do elementów tego obiektu (a jeśli nie, to dlaczego przekażesz ten obiekt?) Nadal się złamie ...Z punktu widzenia łatwości konserwacji argumenty powinny być wyraźnie odróżnialne, najlepiej na poziomie kompilatora.
Pierwszy projekt prowadzi do wczesnego wykrywania błędów. Drugi projekt może prowadzić do subtelnych problemów w czasie wykonywania, które nie pojawiają się w testach. Dlatego należy preferować pierwszy projekt.
źródło
Spośród nich preferuję pierwszą metodę:
function insertIntoDatabase(Account account, Otherthing thing) { database.insertMethod(account.getId(), thing.getId(), thing.getSomeValue()); }
Powodem jest to, że zmiany dokonane w dowolnym obiekcie w dół drogi, o ile zmiany zachowują te pobierające, więc zmiana jest przezroczysta na zewnątrz obiektu, masz mniej kodu do zmiany i przetestowania oraz mniejsze prawdopodobieństwo zakłócenia działania aplikacji.
To tylko mój proces myślowy, oparty głównie na tym, jak lubię pracować i konstruować rzeczy tego rodzaju i które na dłuższą metę okazują się łatwe do zarządzania i konserwacji.
Nie zamierzam wdawać się w konwencje nazewnictwa, ale zwrócę uwagę, że chociaż w tej metodzie jest słowo „baza danych”, ten mechanizm przechowywania może się zmienić. Z pokazanego kodu nic nie wiąże tej funkcji z używaną platformą do przechowywania baz danych - a nawet jeśli jest to baza danych. Po prostu założyliśmy, ponieważ jest w nazwie. Ponownie, zakładając, że te pobierające są zawsze zachowane, zmiana sposobu / miejsca przechowywania tych obiektów będzie łatwa.
Chciałbym jednak ponownie przemyśleć funkcję i dwa obiekty, ponieważ masz funkcję, która jest zależna od dwóch struktur obiektów, a konkretnie zastosowanych modułów pobierających. Wygląda również na to, że ta funkcja wiąże te dwa obiekty w jedną kumulatywną rzecz, która się utrwala. Moje wnętrzności mówią mi, że trzeci obiekt może mieć sens. Musiałbym dowiedzieć się więcej o tych obiektach i ich związku w rzeczywistości i przewidywanej mapie drogowej. Ale moje jelita pochylają się w tym kierunku.
W obecnym stanie kodu pojawia się pytanie „Gdzie powinna lub powinna być ta funkcja?” Czy jest to część konta, czy innego? Dokąd to zmierza?
Wydaje mi się, że istnieje już „baza danych” trzeciego obiektu, a ja skłaniam się ku umieszczeniu tej funkcji w tym obiekcie, a potem staje się zadaniem tych obiektów, aby móc obsługiwać konto i inny element, przekształcić, a następnie zachować wynik .
Jeśli posuniesz się tak daleko, aby ten trzeci obiekt był zgodny ze wzorcem mapowania relacyjno-obiektowego (ORM), tym lepiej. Byłoby to bardzo oczywiste dla każdego, kto pracuje z kodem, aby zrozumieć „Ach, tutaj konto i OtherThing zostają zmiażdżone i trwają”.
Ale może również mieć sens wprowadzenie czwartego obiektu, który obsługuje zadanie łączenia i przekształcania konta i innego elementu, ale nie obsługuje mechaniki utrzymywania się. Zrobiłbym to, jeśli przewidujesz znacznie więcej interakcji z tymi dwoma obiektami lub między nimi, ponieważ w tym czasie chciałbym, aby bity trwałości zostały rozłożone na obiekt, który zarządza tylko trwałością.
Zrobiłbym zdjęcia, aby zachować projekt w taki sposób, aby dowolne konto, inny obiekt lub trzeci obiekt ORM można było zmienić bez konieczności zmiany pozostałych trzech. Chyba że istnieje dobry powód, aby tego nie robić, chciałbym, aby Account i OtherThing były niezależne i nie musiały znać się od siebie nawzajem.
Oczywiście, gdybym wiedział, że tak będzie w pełnym kontekście, mógłbym całkowicie zmienić swoje pomysły. Znowu tak właśnie myślę, kiedy widzę takie rzeczy i jak szczupły.
źródło
Oba podejścia mają swoje zalety i wady. To, co jest lepsze w scenariuszu, zależy w dużej mierze od danego przypadku użycia.
Pro Wiele parametrów, Con Referencje obiektu:
Referencje Pro Object:
Czego więc należy użyć i kiedy wiele zależy od przypadków użycia
źródło
Z jednej strony masz konto i obiekt Otherthing. Z drugiej strony masz możliwość wstawienia wartości do bazy danych, biorąc pod uwagę identyfikator konta i identyfikator Otherthing. To dwie podane rzeczy.
Możesz napisać metodę, biorąc pod uwagę Account i Otherthing jako argumenty. Po stronie pro, osoba dzwoniąca nie musi znać żadnych szczegółów na temat konta i innych elementów. Z drugiej strony, odbiorca musi wiedzieć o metodach Konta i Innych. Ponadto nie ma sposobu, aby wstawić cokolwiek innego do bazy danych niż wartość obiektu Otherthing i nie ma sposobu na użycie tej metody, jeśli masz identyfikator obiektu konta, ale nie sam obiekt.
Lub możesz napisać metodę przyjmującą dwa argumenty i wartość jako argumenty. Z drugiej strony, osoba dzwoniąca musi znać szczegóły konta i inne. I może zdarzyć się sytuacja, w której potrzebujesz więcej szczegółów konta lub innego niż tylko identyfikatora, aby wstawić do bazy danych, w którym to przypadku to rozwiązanie jest całkowicie bezużyteczne. Z drugiej strony, miejmy nadzieję, że nie jest wymagana znajomość konta i innych rzeczy w callee, i jest większa elastyczność.
Twoja opinia: czy potrzebna jest większa elastyczność? Często nie jest to kwestia jednego połączenia, ale byłoby spójne w całym oprogramowaniu: albo używasz identyfikatorów konta, albo używasz obiektów. Mieszanie go daje ci najgorsze z obu światów.
W C ++ możesz mieć metodę przyjmującą dwa identyfikatory id plus wartość oraz metodę wbudowaną uwzględniającą Account i Otherthing, więc masz obie drogi bez narzutu.
źródło