Sporadycznie spotykam metody o niewygodnej liczbie parametrów. Najczęściej wydają się być konstruktorami. Wygląda na to, że powinien być lepszy sposób, ale nie widzę, co to jest.
return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
Myślałem o użyciu struktur do reprezentowania listy parametrów, ale wydaje się, że przenosi to problem z jednego miejsca w drugie i tworzy w procesie inny typ.
ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);
Więc to nie wygląda na poprawę. Więc jakie jest najlepsze podejście?
refactoring
rekurencyjny
źródło
źródło
Odpowiedzi:
Najlepszym sposobem byłoby znalezienie sposobów na pogrupowanie argumentów. Zakłada się, i naprawdę działa tylko wtedy, gdy skończyłoby się z wieloma „grupami” argumentów.
Na przykład, jeśli przekazujesz specyfikację prostokąta, możesz przekazać x, y, szerokość i wysokość lub możesz po prostu przekazać obiekt prostokątny, który zawiera x, y, szerokość i wysokość.
Szukaj takich rzeczy podczas refaktoryzacji, aby nieco to uporządkować. Jeśli naprawdę nie można połączyć argumentów, zacznij sprawdzać, czy nie naruszyłeś zasady pojedynczej odpowiedzialności.
źródło
Zakładam, że masz na myśli C # . Niektóre z tych rzeczy odnoszą się również do innych języków.
Masz kilka możliwości:
przełącz się z konstruktora na ustawiające właściwości . Może to uczynić kod bardziej czytelnym, ponieważ dla czytelnika jest oczywiste, która wartość odpowiada jakim parametrom. Składnia inicjatora obiektu sprawia, że wygląda to ładnie. Jest również prosty w implementacji, ponieważ możesz po prostu użyć właściwości generowanych automatycznie i pominąć pisanie konstruktorów.
Jednak tracisz niezmienność i tracisz możliwość upewnienia się, że wymagane wartości są ustawione przed użyciem obiektu w czasie kompilacji.
Wzorzec konstruktora .
Pomyśl o relacji między
string
aStringBuilder
. Możesz to otrzymać na własne zajęcia. Lubię implementować go jako klasę zagnieżdżoną, więc klasaC
ma powiązaną klasęC.Builder
. Podoba mi się też płynny interfejs kreatora. Zrobione dobrze, możesz uzyskać taką składnię:Mam skrypt PowerShell, który pozwala mi wygenerować kod konstruktora, aby zrobić to wszystko, gdzie dane wejściowe wyglądają następująco:
Więc mogę generować w czasie kompilacji.
partial
klasy pozwalają mi rozszerzyć zarówno klasę główną, jak i konstruktora bez modyfikowania wygenerowanego kodu.Refaktoryzacja „Wprowadź obiekt parametru” . Zobacz katalog refaktoryzacji . Pomysł polega na tym, że bierzesz część przekazywanych parametrów i umieszczasz je w nowym typie, a następnie przekazujesz instancję tego typu. Jeśli zrobisz to bez zastanowienia, wrócisz tam, gdzie zacząłeś:
staje się
Jednak to podejście ma największy potencjał, aby wywrzeć pozytywny wpływ na Twój kod. Więc kontynuuj, wykonując następujące kroki:
Poszukaj podzbiorów parametrów, które mają sens razem. Samo bezmyślne grupowanie wszystkich parametrów funkcji nie daje wiele; celem jest stworzenie sensownych grup. Dowiesz się, że masz rację, gdy nazwa nowego typu będzie oczywista.
Poszukaj innych miejsc, w których te wartości są używane razem, i użyj tam również nowego typu. Są szanse, że kiedy znajdziesz dobry nowy typ dla zestawu wartości, których już używasz w każdym miejscu, ten nowy typ będzie miał również sens we wszystkich tych miejscach.
Poszukaj funkcji, która znajduje się w istniejącym kodzie, ale należy do nowego typu.
Na przykład może zobaczysz kod, który wygląda następująco:
Można podjąć
minSpeed
imaxSpeed
parametry i umieścić je w nowym rodzaju:To jest lepsze, ale aby naprawdę wykorzystać nowy typ, przenieś porównania do nowego typu:
A teraz gdzieś
SpeedIsAcceptable()
dochodzimy : implementacja now mówi o tym, co masz na myśli, i masz użyteczną klasę wielokrotnego użytku. (Następnym oczywistym krokiem jest zrobienieSpeedRange
tegoRange<Speed>
.)Jak widać, wprowadzenie obiektu parametru było dobrym początkiem, ale jego prawdziwą wartością było to, że pomogło nam odkryć przydatny typ, którego brakowało w naszym modelu.
źródło
Jeśli jest to konstruktor, szczególnie jeśli istnieje wiele przeciążonych wariantów, powinieneś spojrzeć na wzorzec Builder:
Jeśli jest to normalna metoda, powinieneś pomyśleć o relacjach między przekazywanymi wartościami i być może utworzyć obiekt transferu.
źródło
Klasyczną odpowiedzią na to jest użycie klasy do hermetyzacji niektórych lub wszystkich parametrów. W teorii brzmi to świetnie, ale jestem typem faceta, który tworzy zajęcia dla pojęć mających znaczenie w tej dziedzinie, więc nie zawsze łatwo jest zastosować tę radę.
Np. Zamiast:
Możesz użyć
YMMV
źródło
Cytat z książki Fowlera i Becka: „Refaktoryzacja”
źródło
Nie chcę brzmieć jak mądry crack, ale powinieneś także sprawdzić, czy dane, które przekazujesz, naprawdę powinny zostać przekazane: Przekazywanie rzeczy konstruktorowi (lub metodzie w tym zakresie) pachnie trochę jak mały nacisk na zachowanie obiektu.
Nie zrozumcie mnie źle: Metody i konstruktorzy będą mieć dużo parametrów czasem. Ale kiedy napotkasz, spróbuj rozważyć hermetyzację danych za pomocą zachowania zamiast tego .
Ten rodzaj zapachu (skoro mówimy o refaktoryzacji, to okropne słowo wydaje się odpowiednie ...) może być również wykrywany dla obiektów, które mają wiele (czytaj: dowolnych) właściwości lub metody pobierające / ustawiające.
źródło
Kiedy widzę długie listy parametrów, moje pierwsze pytanie dotyczy tego, czy ta funkcja lub obiekt robi za dużo. Rozważać:
Oczywiście ten przykład jest celowo śmieszny, ale widziałem wiele prawdziwych programów z przykładami tylko nieco mniej absurdalnymi, w których jedna klasa jest używana do przechowywania wielu ledwo powiązanych lub niepowiązanych rzeczy, najwyraźniej tylko dlatego, że ten sam program wywołujący wymaga obu lub ponieważ programista pomyślał o obu jednocześnie. Czasami prostym rozwiązaniem jest podzielenie klasy na wiele części, z których każda robi swoje.
Nieco bardziej skomplikowane jest sytuacja, gdy klasa naprawdę musi zajmować się wieloma logicznymi rzeczami, takimi jak zamówienie klienta i ogólne informacje o kliencie. W takich przypadkach stwórz klasę dla klienta i klasę na zamówienie i pozwól im rozmawiać ze sobą w razie potrzeby. Więc zamiast:
Moglibyśmy mieć:
Chociaż oczywiście wolę funkcje, które przyjmują tylko 1, 2 lub 3 parametry, czasami musimy zaakceptować, że realistycznie rzecz biorąc, ta funkcja zajmuje sporo, a sama liczba nie powoduje tak naprawdę złożoności. Na przykład:
Tak, jest to kilka pól, ale prawdopodobnie wszystko, co z nimi zrobimy, to zapisać je w rekordzie bazy danych lub wrzucić na ekran lub coś takiego. Nie ma tu zbyt wiele przetwarzania.
Kiedy moje listy parametrów robią się długie, wolę, jeśli mogę nadać polom różne typy danych. Na przykład, gdy widzę funkcję taką jak:
A potem widzę, jak nazywa się to:
Martwię się. Patrząc na wezwanie, nie jest wcale jasne, co oznaczają te wszystkie tajemnicze liczby, kody i flagi. To tylko prośba o błędy. Programista może łatwo się pomylić co do kolejności parametrów i przypadkowo przełączyć dwa, a jeśli są tego samego typu danych, kompilator po prostu by to zaakceptował. Wolałbym raczej mieć podpis, w którym wszystkie te rzeczy są wyliczeniami, więc wywołanie przechodzi w takich rzeczach, jak Type.ACTIVE zamiast „A” i CreditWatch.NO zamiast „false” itp.
źródło
Jeśli niektóre parametry konstruktora są opcjonalne, sensowne jest użycie konstruktora, który pobierałby wymagane parametry w konstruktorze i miał metody dla opcjonalnych, zwracających konstruktora, do użycia w następujący sposób:
Szczegóły tego są opisane w Effective Java, 2nd Ed., Str. 11. Jeśli chodzi o argumenty metod, ta sama książka (s. 189) opisuje trzy podejścia do skracania list parametrów:
DinoDonkey
zamiastdino
idonkey
źródło
Użyłbym domyślnego konstruktora i ustawiaczy właściwości. C # 3.0 ma niezłą składnię, dzięki której można to zrobić automagicznie.
Ulepszenie kodu polega na uproszczeniu konstruktora i braku konieczności obsługi wielu metod do obsługi różnych kombinacji. Składnia „wywoływania” jest nadal trochę „rozwlekła”, ale tak naprawdę nie jest gorsza niż ręczne wywoływanie ustawników właściwości.
źródło
Nie dostarczyłeś wystarczających informacji, aby uzasadnić dobrą odpowiedź. Długa lista parametrów nie jest z natury zła.
można interpretować jako:
W takim przypadku o wiele lepiej jest utworzyć klasę do hermetyzacji parametrów, ponieważ nadajesz znaczenie różnym parametrom w sposób, który kompilator może sprawdzić, a także wizualnie ułatwiając odczytanie kodu. Ułatwia również późniejsze czytanie i refaktoryzację.
Alternatywnie, jeśli miałeś:
To zupełnie inny przypadek, ponieważ wszystkie obiekty są różne (i prawdopodobnie nie zostaną pomieszane). Zgodzono się, że jeśli wszystkie obiekty są potrzebne i wszystkie są różne, tworzenie klasy parametrów nie ma sensu.
Dodatkowo, czy niektóre parametry są opcjonalne? Czy istnieją nadpisania metody (ta sama nazwa metody, ale różne sygnatury metod?) Te rodzaje szczegółów mają znaczenie dla tego, co jest najlepsze odpowiedzi.
* Torba na własność może być również przydatna, ale nie jest lepsza, biorąc pod uwagę, że nie ma podanego tła.
Jak widać, jest więcej niż jedna poprawna odpowiedź na to pytanie. Wybieraj.
źródło
Możesz spróbować zgrupować swój parametr w wiele znaczących struktur / klas (jeśli to możliwe).
źródło
Generalnie skłaniałbym się ku podejściu structs - przypuszczalnie większość tych parametrów jest w jakiś sposób powiązanych i reprezentuje stan jakiegoś elementu, który jest istotny dla twojej metody.
Jeśli zestawu parametrów nie można przekształcić w znaczący obiekt, prawdopodobnie jest to znak, że
Shniz
robi za dużo, a refaktoryzacja powinna obejmować rozbicie metody na osobne zagadnienia.źródło
Możesz handlować złożonością liniami kodu źródłowego. Jeśli sama metoda robi za dużo (nóż szwajcarski), spróbuj zmniejszyć o połowę jej zadania, tworząc inną metodę. Jeśli metoda jest prosta, tylko że wymaga zbyt wielu parametrów, to najlepszym rozwiązaniem są tzw. Obiekty parametrów.
źródło
Jeśli twój język to obsługuje, użyj nazwanych parametrów i ustaw jak najwięcej opcjonalnych (z rozsądnymi wartościami domyślnymi).
źródło
Myślę, że metoda, którą opisałeś, jest właściwą drogą. Kiedy znajduję metodę z wieloma parametrami i / lub taką, która prawdopodobnie będzie potrzebować więcej w przyszłości, zwykle tworzę obiekt ShnizParams do przejścia, tak jak opisujesz.
źródło
Co powiesz na to, aby nie ustawiać tego od razu w konstruktorach, ale robiąc to za pomocą właściwości / ustawiających ? Widziałem kilka klas .NET, które wykorzystują to podejście, na przykład
Process
class:źródło
Zgadzam się z podejściem przenoszenia parametrów do obiektu parametrycznego (struktury). Zamiast jednak trzymać je wszystkie w jednym obiekcie, sprawdź, czy inne funkcje używają podobnych grup parametrów. Obiekt paramater jest bardziej wartościowy, jeśli jest używany z wieloma funkcjami, w przypadku których oczekuje się, że zestaw parametrów będzie się konsekwentnie zmieniać w tych funkcjach. Może się zdarzyć, że wstawisz tylko niektóre parametry do nowego obiektu parametrów.
źródło
Jeśli masz tak wiele parametrów, istnieje prawdopodobieństwo, że metoda robi zbyt dużo, więc najpierw zajmij się tym, dzieląc metodę na kilka mniejszych metod. Jeśli po tym nadal masz zbyt wiele parametrów, spróbuj zgrupować argumenty lub przekształcić niektóre parametry w elementy członkowskie instancji.
Preferuj małe klasy / metody od dużych. Pamiętaj o zasadzie pojedynczej odpowiedzialności.
źródło
Nazwane argumenty są dobrą opcją (zakładając język, który je obsługuje) do ujednoznaczniania długich (lub nawet krótkich!) List parametrów, jednocześnie pozwalając (w przypadku konstruktorów) na niezmienność właściwości klasy bez narzucania wymogu zezwalania na jej istnienie w stanie częściowo zbudowanym.
Inną opcją, której szukałbym przy tego rodzaju refaktorze, byłyby grupy powiązanych parametrów, które mogłyby być lepiej obsługiwane jako niezależny obiekt. Używając klasy Rectangle z wcześniejszej odpowiedzi jako przykładu, konstruktor, który pobiera parametry dla x, y, wysokość i szerokość, może rozłożyć x i y do obiektu Point, umożliwiając przekazanie trzech parametrów do konstruktora Rectangle. Lub pójdź trochę dalej i ustaw dwa parametry (UpperLeftPoint, LowerRightPoint), ale to byłaby bardziej radykalna refaktoryzacja.
źródło
To zależy od tego, jakie masz argumenty, ale jeśli są to dużo wartości / opcji logicznych, może możesz użyć wyliczenia flag?
źródło
Myślę, że ten problem jest głęboko powiązany z domeną problemu, który próbujesz rozwiązać z klasą.
W niektórych przypadkach konstruktor składający się z 7 parametrów może wskazywać na złą hierarchię klas: w takim przypadku struktura / klasa pomocnicza sugerowana powyżej jest zwykle dobrym podejściem, ale wtedy również kończy się na wielu strukturach, które są po prostu zbiorami właściwości i nie rób nic pożytecznego. 8-argumentowy konstruktor może również wskazywać, że twoja klasa jest zbyt ogólna / uniwersalna, więc wymaga wielu opcji, aby była naprawdę użyteczna. W takim przypadku możesz albo refaktoryzować klasę, albo zaimplementować statyczne konstruktory, które ukrywają rzeczywiste złożone konstruktory: np. Shniz.NewBaz (foo, bar) może w rzeczywistości wywołać prawdziwy konstruktor przekazujący odpowiednie parametry.
źródło
Należy wziąć pod uwagę, która z wartości będzie tylko do odczytu po utworzeniu obiektu?
Być może po skonstruowaniu można by przypisać właściwości do publicznego zapisu.
Skąd ostatecznie pochodzą wartości? Być może niektóre wartości są naprawdę zewnętrzne, podczas gdy inne pochodzą z jakiejś konfiguracji lub danych globalnych utrzymywanych przez bibliotekę.
W takim przypadku możesz ukryć konstruktor przed zewnętrznym użyciem i udostępnić mu funkcję Create. Funkcja create pobiera prawdziwie zewnętrzne wartości i konstruuje obiekt, a następnie używa akcesorów dostępnych tylko w bibliotece do ukończenia tworzenia obiektu.
Byłoby naprawdę dziwne, gdyby obiekt wymagał 7 lub więcej parametrów, aby nadać obiektowi kompletny stan, a wszystkie są z natury zewnętrzne.
źródło
Gdy klasa ma konstruktora, który przyjmuje zbyt wiele argumentów, zwykle jest to znak, że ma zbyt wiele obowiązków. Prawdopodobnie można go podzielić na osobne klasy, które współpracują ze sobą, aby nadać te same funkcje.
W przypadku, gdy naprawdę potrzebujesz tak wielu argumentów do konstruktora, wzorzec Builder może ci pomóc. Celem jest nadal przekazywanie wszystkich argumentów do konstruktora, więc jego stan jest inicjowany od samego początku i nadal można uczynić klasę niezmienną w razie potrzeby.
Zobacz poniżej:
źródło