Najlepsza praktyka przekazywania wielu argumentów do metody?

95

Czasami musimy napisać metody otrzymujące wiele argumentów, na przykład:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Kiedy napotykam tego rodzaju problem, często zamykam argumenty w mapie.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

To nie jest dobra praktyka, hermetyzacja parametrów w mapie to totalna strata wydajności. Dobrą rzeczą jest to, że czysty podpis, łatwe dodawanie innych parametrów przy jak najmniejszej liczbie modyfikacji. jaka jest najlepsza praktyka w przypadku tego rodzaju problemu?

Tracz
źródło

Odpowiedzi:

140

W Effective Java , Rozdział 7 (Metody), punkt 40 (Ostrożnie projektować sygnatury metod), Bloch pisze:

Istnieją trzy techniki skracania zbyt długich list parametrów:

  • podzielić metodę na wiele metod, z których każda wymaga tylko podzbioru parametrów
  • utwórz klasy pomocnicze do przechowywania grup parametrów (zazwyczaj statyczne klasy składowe)
  • dostosuj wzorzec Builder z konstrukcji obiektu do wywołania metody.

Po więcej szczegółów zachęcam do zakupu książki, naprawdę warto.

JRL
źródło
Co to jest „zbyt długie parametry”? Kiedy można powiedzieć, że metoda ma zbyt wiele parametrów? Czy jest określona liczba lub zakres?
Red M
2
@RedM Zawsze uważałem, że coś więcej niż 3 lub 4 parametry jest „zbyt długie”
jtate
2
@jtate to osobisty wybór, czy śledzisz oficjalny dokument?
Red M
2
@RedM osobiste preferencje :)
jtate
2
W trzecim wydaniu Effective Java jest to rozdział 8 (metody), pozycja 51
GarethOwen
71

Używanie mapy z magicznymi klawiszami String to zły pomysł. Tracisz czas kompilacji i naprawdę nie jest jasne, jakie są wymagane parametry. Aby to nadrobić, musiałbyś napisać bardzo kompletną dokumentację. Czy za kilka tygodni przypomnisz sobie, czym są te ciągi bez patrzenia na kod? A co jeśli popełnisz literówkę? Użyć niewłaściwego typu? Nie dowiesz się, dopóki nie uruchomisz kodu.

Zamiast tego użyj modelu. Utwórz klasę, która będzie kontenerem dla wszystkich tych parametrów. W ten sposób zachowujesz bezpieczeństwo typów Java. Możesz również przekazać ten obiekt innym metodom, umieścić go w kolekcjach itp.

Oczywiście, jeśli zestaw parametrów nie jest używany gdzie indziej ani przekazywany, dedykowany model może być przesadą. Trzeba znaleźć równowagę, więc kieruj się zdrowym rozsądkiem.

Tomek
źródło
24

Jeśli masz wiele opcjonalnych parametrów, możesz stworzyć płynne API: zastąp pojedynczą metodę łańcuchem metod

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Korzystając z importu statycznego, możesz tworzyć wewnętrzne interfejsy API:

... .datesBetween(from(date1).to(date2)) ...
Ha.
źródło
3
A jeśli wszystkie parametry są wymagane, a nie opcjonalne?
emeraldhieu
1
W ten sposób możesz również mieć parametry domyślne. Również wzór budowniczym jest związane z biegle interfejsów. Myślę, że to naprawdę powinna być odpowiedź. Oprócz rozbicia długiego konstruktora na mniejsze metody inicjalizacji, które są opcjonalne.
Ehtesh Choudhury
13

Nazywa się „Wprowadź obiekt parametru”. Jeśli zauważysz, że przekazujesz tę samą listę parametrów w kilku miejscach, po prostu utwórz klasę, która będzie przechowywać je wszystkie.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Nawet jeśli nie zdarza się, że często przekazujesz tę samą listę parametrów, ta łatwa refaktoryzacja nadal poprawi czytelność kodu, co zawsze jest dobre. Jeśli spojrzysz na swój kod 3 miesiące później, łatwiej będzie ci zrozumieć, kiedy musisz naprawić błąd lub dodać funkcję.

To oczywiście ogólna filozofia, a ponieważ nie podałeś żadnych szczegółów, nie mogę też udzielić ci bardziej szczegółowych porad. :-)

dimitarvp
źródło
czy wyrzucanie elementów bezużytecznych będzie problemem?
rupinderjeet
1
Nie, jeśli ustawisz lokalny zasięg obiektu parametru w funkcji wywołującej i nie zmienisz go. W takich okolicznościach najprawdopodobniej zostanie on zebrany, a jego pamięć zostanie ponownie wykorzystana.
dimitarvp
Imo, powinieneś także mieć XXXParameter param = new XXXParameter();dostępny, a następnie użyć XXXParameter.setObjA(objA); etc ...
satibel
10

Najpierw spróbuję zmienić metodę. Jeśli używa tak wielu parametrów, może być w jakikolwiek sposób zbyt długi. Zerwanie go poprawiłoby kod i potencjalnie zmniejszyłoby liczbę parametrów każdej metody. Możesz również mieć możliwość refaktoryzacji całej operacji do jej własnej klasy. Po drugie, szukałbym innych przypadków, w których używam tej samej (lub nadzbiór) tej samej listy parametrów. Jeśli masz wiele instancji, prawdopodobnie oznacza to, że te właściwości należą do siebie. W takim przypadku utwórz klasę do przechowywania parametrów i użyj jej. Na koniec oceniłbym, czy liczba parametrów sprawia, że ​​warto utworzyć obiekt mapy, aby poprawić czytelność kodu. Myślę, że to sprawa osobista - to rozwiązanie sprawia ból w każdym przypadku, a punkt kompromisowy może się różnić. Dla sześciu parametrów prawdopodobnie bym tego nie zrobił. Dla 10 prawdopodobnie tak bym (jeśli żadna z pozostałych metod nie zadziałała jako pierwsza).

tvanfosson
źródło
8

Jest to często problem przy konstruowaniu obiektów.

W takim przypadku użyj wzorca obiektu budującego , działa dobrze, jeśli masz dużą listę parametrów i nie zawsze potrzebujesz ich wszystkich.

Możesz również dostosować go do wywołania metody.

Znacznie zwiększa też czytelność.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

Możesz również umieścić logikę weryfikacji w metodach set .. () i build () Builder.

Bohdan
źródło
Co byś polecił, jeśli masz wiele dziedzin final? To jest główna rzecz, która powstrzymuje mnie przed pisaniem funkcji pomocniczych. Przypuszczam, że mogę uczynić pola prywatnymi i upewnić się, że nie zmodyfikuję ich niepoprawnie w kodzie tej klasy, ale mam nadzieję na coś bardziej eleganckiego.
ragerdl
7

Istnieje wzorzec nazywany obiektem parametru .

Chodzi o to, aby zamiast wszystkich parametrów użyć jednego obiektu. Teraz, nawet jeśli musisz później dodać parametry, wystarczy dodać je do obiektu. Interfejs metody pozostaje taki sam.

Padmarag
źródło
5

Możesz stworzyć klasę do przechowywania tych danych. Musi być jednak wystarczająco znaczący, ale znacznie lepszy niż używanie mapy (OMG).

Johannes Rudolph
źródło
Nie sądzę, aby było konieczne tworzenie klasy do przechowywania parametru metody.
Sawyer
Utworzyłbym klasę tylko wtedy, gdyby było wiele przypadków przekazywania tych samych parametrów. To sygnalizowałoby, że parametry są powiązane i prawdopodobnie i tak należą do siebie. Jeśli tworzysz klasę dla jednej metody, lekarstwo jest prawdopodobnie gorsze niż choroba.
tvanfosson
Tak - możesz przenieść powiązane parametry do DTO lub obiektu wartości. Czy niektóre z wielu parametrów są opcjonalne, tj. Główna metoda jest przeciążona tymi dodatkowymi parametrami? W takich przypadkach - czuję, że jest to dopuszczalne.
JoseK
To właśnie miałem na myśli mówiąc, że musi być wystarczająco znaczące.
Johannes Rudolph
4

Code Complete * sugeruje kilka rzeczy:

  • „Ogranicz liczbę parametrów procedury do około siedmiu. Siedem to magiczna liczba dla ludzkiego zrozumienia” (str. 108).
  • „Umieść parametry w kolejności wejście-modyfikacja-wyjście ... Jeśli kilka procedur używa podobnych parametrów, ustaw podobne parametry w stałej kolejności” (str. 105).
  • Umieść zmienne statusu lub błędu na końcu.
  • Jak wspomniano w tvanfosson , przekazuj tylko te części zmiennych strukturalnych (obiektów), których potrzebuje procedura. To powiedziawszy, jeśli używasz większości zmiennej strukturalnej w funkcji, po prostu przekaż całą strukturę, ale pamiętaj, że do pewnego stopnia sprzyja to sprzężeniu.

* Pierwsza edycja, wiem, że powinienem zaktualizować. Jest również prawdopodobne, że niektóre z tych porad mogły ulec zmianie od czasu napisania drugiej edycji, gdy OOP zaczęło zyskiwać na popularności.

Justin Johnson
źródło
2

Dobra praktyka to refaktoryzacja. Co z tymi obiektami oznacza, że ​​należy je przekazać do tej metody? Czy powinny być zamknięte w jednym obiekcie?

GaryF
źródło
tak, powinni. Na przykład duży formularz wyszukiwania ma wiele niepowiązanych ograniczeń i wymaga podziału na strony. potrzebujesz przekazać bieżący numer strony, kryteria wyszukiwania, rozmiar strony ...
Sawyer
2

Korzystanie z mapy to prosty sposób na wyczyszczenie sygnatury wywołania, ale pojawia się inny problem. Musisz zajrzeć do treści metody, aby zobaczyć, czego metoda oczekuje w tej Mapie, jakie są nazwy kluczy lub jakie typy mają wartości.

Czystszym sposobem byłoby zgrupowanie wszystkich parametrów w komponencie bean obiektu, ale to nadal nie rozwiązuje całkowicie problemu.

To, co tu masz, to kwestia projektu. Mając więcej niż 7 parametrów metody, zaczniesz mieć problemy z zapamiętaniem, co one reprezentują i jaką mają kolejność. Stąd otrzymasz wiele błędów, po prostu wywołując metodę w złej kolejności parametrów.

Potrzebujesz lepszego projektu aplikacji, a nie najlepszej praktyki, aby przesyłać wiele parametrów.


źródło
1

Utwórz klasę bean i ustaw wszystkie parametry (metoda setter) i przekaż ten obiekt bean do metody.

Tony
źródło
1
  • Spójrz na swój kod i zobacz, dlaczego wszystkie te parametry są przekazywane. Czasami można zrefaktoryzować samą metodę.

  • Korzystanie z mapy powoduje, że metoda jest podatna na ataki. Co się stanie, jeśli ktoś używający twojej metody źle przeliteruje nazwę parametru lub opublikuje ciąg znaków, w którym twoja metoda oczekuje UDT?

  • Zdefiniuj obiekt transferu . Zapewni ci to przynajmniej sprawdzanie typów; może nawet być możliwe wykonanie jakiejś walidacji w miejscu użycia zamiast w ramach metody.

Wszyscy
źródło
0

Często oznacza to, że twoja klasa ponosi więcej niż jedną odpowiedzialność (tj. Twoja klasa robi ZA dużo).

Zobacz Zasada pojedynczej odpowiedzialności

dla dalszych szczegółów.

helpermethod
źródło
0

Jeśli przekazujesz zbyt wiele parametrów, spróbuj refaktoryzować metodę. Może robi wiele rzeczy, których nie powinien robić. Jeśli tak nie jest, spróbuj zastąpić parametry jedną klasą. W ten sposób możesz hermetyzować wszystko w jednej instancji klasy i przekazywać instancję, a nie parametry.

azamsharp
źródło
0

Powiedziałbym, że trzymaj się tego, jak robiłeś to wcześniej. Liczba parametrów w twoim przykładzie nie jest duża, ale alternatywy są znacznie straszniejsze.

  1. Mapa - jest kwestia wydajności, o której wspomniałeś, ale większy problem to:

    • Dzwoniący nie wiedzą, co wysłać, nie odwołując się do czegoś
      innego ... Czy masz javadoc, który dokładnie określa, jakie klucze i
      wartości są używane? Jeśli to zrobisz (co jest świetne), posiadanie wielu parametrów również nie stanowi problemu.
    • Bardzo trudno jest zaakceptować różne typy argumentów. Możesz ograniczyć parametry wejściowe do jednego typu lub użyć Map <String, Object> i rzutować wszystkie wartości. Obie opcje są okropne przez większość czasu.
  2. Obiekty opakowujące - to po prostu przenosi problem, ponieważ w pierwszej kolejności musisz wypełnić obiekt opakowujący - zamiast bezpośrednio do metody, będzie to konstruktor obiektu parametru. Ustalenie, czy przeniesienie problemu jest właściwe, czy nie, zależy od ponownego wykorzystania wspomnianego obiektu. Na przykład:

Nie użyłbym tego: byłby użyty tylko raz przy pierwszym wywołaniu, więc dużo dodatkowego kodu do obsługi 1 linii ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Może to wykorzystać: tutaj może zdziałać trochę więcej. Po pierwsze, może uwzględniać parametry dla 3 wywołań metod. może również samodzielnie wykonywać 2 inne linie ... więc w pewnym sensie staje się zmienną stanu ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Wzorzec budowniczy - moim zdaniem jest to anty-wzór. Najbardziej pożądanym mechanizmem obsługi błędów jest wykrywanie wcześniej, a nie później; ale w przypadku wzorca konstruktora wywołania z brakującymi parametrami obowiązkowymi (programista nie pomyślał o ich dołączeniu) są przenoszone z czasu kompilacji do czasu wykonania. Oczywiście, jeśli programista celowo umieści wartość null lub coś podobnego w slocie, to będzie to uruchomione, ale mimo to wcześniejsze wyłapywanie błędów jest znacznie większą zaletą dla programistów, którzy odmawiają spojrzenia na nazwy parametrów wywoływanej metody. Uważam, że jest to właściwe tylko w przypadku dużej liczby parametrów opcjonalnych , a nawet wtedy korzyść jest w najlepszym przypadku marginalna. Jestem bardzo przeciwny „wzorowi” budowniczego.

Inną rzeczą, o której ludzie zapominają, jest rola IDE w tym wszystkim. Gdy metody mają parametry, środowiska IDE generują większość kodu za Ciebie, a czerwone linie przypominają Ci, co musisz podać / ustawić. Korzystając z opcji 3 ... całkowicie to tracisz. Teraz od programisty zależy, czy zrobi to dobrze, i nie ma żadnych wskazówek podczas kodowania i kompilacji ... programista musi to przetestować, aby się dowiedzieć.

Ponadto warianty 2 i 3, jeśli zostaną niepotrzebnie przyjęte szeroko, mają długoterminowe negatywne konsekwencje w zakresie utrzymania ze względu na dużą liczbę duplikatów kodu, które generuje. Im więcej kodu, tym więcej jest do utrzymania, tym więcej czasu i pieniędzy przeznacza się na jego utrzymanie.

Jan
źródło