Zarządzanie konstruktorami z wieloma parametrami w Javie

106

W niektórych naszych projektach istnieje hierarchia klas, która dodaje więcej parametrów w miarę postępu w łańcuchu. Na dole, niektóre klasy mogą mieć do 30 parametrów, z których 28 jest właśnie przekazywanych do super konstruktora.

Przyznam, że używanie automatycznego DI przez coś takiego jak Guice byłoby fajne, ale z pewnych przyczyn technicznych te konkretne projekty są ograniczone do Javy.

Konwencja porządkowania argumentów alfabetycznie według typu nie działa, ponieważ jeśli typ jest refaktoryzowany (koło, które przekazałeś jako argument 2, jest teraz kształtem), może nagle przestać działać.

To pytanie może być zbyt szczegółowe i najeżone krytyką „Jeśli to twój problem, robisz to źle na poziomie projektowania”, ale ja szukam po prostu jakichkolwiek punktów widzenia.

Steve Armstrong
źródło

Odpowiedzi:

265

Może pomóc wzorzec projektowy Builder. Rozważmy następujący przykład

public class StudentBuilder
{
    private String _name;
    private int _age = 14;      // this has a default
    private String _motto = ""; // most students don't have one

    public StudentBuilder() { }

    public Student buildStudent()
    {
        return new Student(_name, _age, _motto);
    }

    public StudentBuilder name(String _name)
    {
        this._name = _name;
        return this;
    }

    public StudentBuilder age(int _age)
    {
        this._age = _age;
        return this;
    }

    public StudentBuilder motto(String _motto)
    {
        this._motto = _motto;
        return this;
    }
}

To pozwala nam pisać kod taki jak

Student s1 = new StudentBuilder().name("Eli").buildStudent();
Student s2 = new StudentBuilder()
                 .name("Spicoli")
                 .age(16)
                 .motto("Aloha, Mr Hand")
                 .buildStudent();

Jeśli pominiemy wymagane pole (przypuszczalnie nazwa jest wymagana), możemy poprosić konstruktora Studenta o zgłoszenie wyjątku. I pozwala nam mieć domyślne / opcjonalne argumenty bez konieczności śledzenia jakiejkolwiek kolejności argumentów, ponieważ każda kolejność tych wywołań będzie działać równie dobrze.

Eli Courtwright
źródło
10
Oczywiście przy imporcie statycznym w ogóle nie musisz nawet „widzieć” tych „konstruktorów”. Na przykład możesz mieć statyczną nazwę metody (nazwa ciągu), która zwraca konstruktora i Student (StudentBuilder), który zwraca studenta. Stąd Student (imię („Joe”). Wiek (15) .motto („Zmoczyłem się”));
oxbow_lakes
2
@oxbow_lakes: W twoim przykładzie, która klasa ma statyczną nazwę metody (nazwę ciągu)?
user443854
Z technicznego punktu widzenia możliwe jest użycie klasy Student do zbudowania nowego ucznia. Dodałem metody w klasie Student i działało dobrze. W ten sposób nie potrzebowałem innej klasy konstruktora. Nie jestem jednak pewien, czy jest to pożądane. Czy istnieje powód, aby używać innej klasy (StudentBuilder) do jej tworzenia?
WVrock
1
@WVrock: To zależy od implementacji. Jak mówię w mojej odpowiedzi, zrobienie tego z samą klasą uczniów może potencjalnie pozostawić klasę w stanie częściowo zainicjowanym, np. Jeśli masz wymagane pole, które nie zostało jeszcze zainicjowane.
Eli Courtwright,
@EliCourtwright Myślę, że chodzi o projektowanie preferencji / kodu. Zamiast zmusić konstruktora do rzucania wyjątku, sprawiłem, że buildStudent()metoda zgłosiła wyjątek.
WVrock
25

Czy możesz hermetyzować powiązane parametry wewnątrz obiektu?

np. jeśli parametry są podobne


MyClass(String house, String street, String town, String postcode, String country, int foo, double bar) {
  super(String house, String street, String town, String postcode, String country);
  this.foo = foo;
  this.bar = bar;

zamiast tego możesz mieć:


MyClass(Address homeAddress, int foo, double bar) {
  super(homeAddress);
  this.foo = foo;
  this.bar = bar;
}

JeeBee
źródło
8

Cóż, użycie wzorca konstruktora może być jednym z rozwiązań.

Ale kiedy dojdziemy do 20 do 30 parametrów, myślę, że istnieje duża zależność między parametrami. Tak więc (jak sugerowano) pakowanie ich w logicznie rozsądne obiekty danych ma prawdopodobnie największy sens. W ten sposób obiekt danych może już sprawdzić ważność ograniczeń między parametrami.

W przypadku wszystkich moich projektów w przeszłości, kiedy doszedłem do punktu, w którym miałem zbyt wiele parametrów (a było to 8, a nie 28!), Byłem w stanie wyczyścić kod, tworząc lepszy model danych.


źródło
4

Ponieważ jesteś ograniczony do języka Java 1.4, jeśli chcesz DI, Spring byłby bardzo przyzwoitą opcją. DI jest pomocne tylko w miejscach, w których parametry konstruktora są usługami lub czymś, co nie zmienia się w czasie wykonywania.

Jeśli masz wszystkie te różne konstruktory ze względu na fakt, że potrzebujesz zmiennych opcji dotyczących konstruowania obiektu, powinieneś poważnie rozważyć użycie wzorca Builder.

Guðmundur Bjarni
źródło
Parametry to głównie usługi, jak wspomniałeś, więc DI jest tym, czego potrzebowałbym. Myślę, że wzorzec Builder wspomniany w kilku innych odpowiedziach jest dokładnie tym, na co liczyłem.
Steve Armstrong
4

Najlepszym rozwiązaniem jest brak zbyt wielu parametrów w konstruktorze. Jedynymi parametrami naprawdę potrzebnymi w konstruktorze są parametry potrzebne do poprawnej inicjalizacji obiektu. Możesz mieć konstruktory z wieloma parametrami, ale także konstruktor z minimalnymi parametrami. Dodatkowe konstruktory wywołują ten prosty konstruktor, a następnie ustawiają inne parametry. W ten sposób możesz uniknąć problemu z łańcuchem z coraz większą liczbą parametrów, ale także mieć kilka wygodnych konstruktorów.

Mnementh
źródło
1

Refaktoryzacja w celu zmniejszenia liczby parametrów i głębokości hierarchii dziedziczenia to prawie wszystko, o czym mogę pomyśleć, ponieważ nic tak naprawdę nie pomoże w utrzymaniu równych 20 parametrów. Będziesz musiał po prostu odebrać każdą rozmowę podczas przeglądania dokumentacji.

Jedną rzeczą, którą możesz zrobić, jest zgrupowanie niektórych logicznie pogrupowanych parametrów w ich własny obiekt wyższego poziomu, ale wiąże się to z własnymi problemami.

Aaron Maenpaa
źródło