Unikanie konstruktorów z wieloma argumentami

10

Mam więc fabrykę, która tworzy obiekty różnych klas. Wszystkie możliwe klasy pochodzą od abstrakcyjnego przodka. Fabryka ma plik konfiguracyjny (składnia JSON) i decyduje, którą klasę utworzyć, w zależności od konfiguracji użytkownika.

Aby to osiągnąć, fabryka używa boost :: property_tree do parsowania JSON. Przechodzi przez ptree i decyduje, który konkretny obiekt stworzyć.

Jednak obiekty produktu mają wiele pól (atrybutów). W zależności od konkretnej klasy obiekt ma około 5-10 atrybutów, w przyszłości może nawet więcej.

Nie jestem więc pewien, jak powinien wyglądać konstruktor obiektów. Mogę wymyślić dwa rozwiązania:

1) Konstruktor produktu oczekuje każdego atrybutu jako parametru, dlatego konstruktor otrzyma ponad 10 parametrów. Będzie to brzydkie i doprowadzi do długich, nieczytelnych linii kodu. Zaletą jest jednak to, że fabryka może przeanalizować JSON i wywołać konstruktor z poprawnymi parametrami. Klasa produktu nie musi wiedzieć, że została utworzona z powodu konfiguracji JSON. Nie musi wiedzieć, że w ogóle jest zaangażowany JSON lub konfiguracja.

2) Konstruktor produktu oczekuje tylko jednego argumentu, obiektu property_tree. Następnie może przeanalizować potrzebne informacje. Jeśli brakuje informacji w konfiguracji lub jest ona poza zakresem, każda klasa produktu może zareagować poprawnie. Fabryka nie musi wiedzieć, jakich argumentów potrzebuje kilka produktów. Fabryka nie musi także wiedzieć, jak zareagować w przypadku nieprawidłowej konfiguracji. Interfejs konstruktora jest zunifikowany i mały. Jednak wadą jest, że produkt musi wyodrębnić potrzebne informacje z JSON, dlatego wie, jak jest skonstruowany.

Wolę rozwiązanie 2). Nie jestem jednak pewien, czy jest to dobry wzór fabryczny. Wydaje się, że to źle, informując produkt, że został utworzony w konfiguracji JSON. Z drugiej strony nowe produkty można wprowadzać bardzo prosto.

Jakieś opinie na ten temat?

lugge86
źródło
1
Śledziłem twój link. W najwyżej ocenianej odpowiedzi od grzechotnika znajduje się przykład. Ale jaki problem rozwiązuje to podejście do „konstruktora”? Istnieje linia danych DataClass = builder.createResult () ;. Ale metoda createResults () nadal musi pobrać 10 parametrów do obiektu DataClass. Ale jak? Wygląda na to, że masz jeszcze jedną warstwę abstrakcji, ale konstruktor DataClass nie zmniejsza się.
lugge86
Zobacz konstruktor i prototyp.
Silviu Burcea,
Silviu Burcea, zrobiłem to. Jednak w przypadku korzystania z konstruktora, w jaki sposób konstruktor wprowadza parametry do produktu? Gdzieś tam musi być gruby interfejs. Konstruktor to tylko jedna warstwa, ale w jakiś sposób parametry muszą znaleźć drogę do klasy produktu.
lugge86
1
Jeśli twoja klasa jest zbyt duża, przestawienie argumentów konstruktora nie sprawi, że nie będzie ona zbyt duża .
Telastyn

Odpowiedzi:

10

Nie zrobiłbym opcji 2, ponieważ wtedy na zawsze rozwinęłeś konstrukcję swojego obiektu za pomocą analizy drzewa właściwości doładowania. Jeśli czujesz się komfortowo z klasą, która potrzebuje tak wielu parametrów, powinieneś czuć się komfortowo z konstruktorem, który potrzebuje tak wielu parametrów, takie jest życie!

Jeśli twoją główną troską jest czytelność kodu, możesz użyć wzorca konstruktora, jest to zasadniczo stopgap c ++ / java z powodu braku nazwanych argumentów. W efekcie powstają rzeczy, które wyglądają tak:

MyObject o = MyObject::Builder()
               .setParam1(val1)
               .setParam2(val2)
               .setParam3(val3)
             .build();

Więc teraz MyObject będzie miał prywatnego konstruktora, który zostanie wywołany w Builder :: build. Zaletą jest to, że będzie to jedyne miejsce, w którym będziesz musiał wywołać konstruktora z 10 parametrami. Fabryka drzewa właściwości doładowania użyje konstruktora, a następnie, jeśli chcesz zbudować MyObject bezpośrednio lub z innego źródła, przejdziesz przez konstruktora. Konstruktor zasadniczo pozwala wyraźnie nazwać każdy parametr podczas przekazywania, dzięki czemu jest bardziej czytelny. To oczywiście dodaje trochę podstawki, więc będziesz musiał zdecydować, czy warto, w porównaniu do zwykłego wywołania niechlujnego konstruktora lub zrzucenia niektórych istniejących parametrów do struktur itp. Po prostu rzucając inną opcję na stół.

https://en.wikipedia.org/wiki/Builder_pattern#C.2B.2B_Example

Nir Friedman
źródło
5

NIE używaj drugiego podejścia.

Z pewnością nie jest to rozwiązanie i prowadziłoby tylko do tworzenia instancji klas w logice biznesowej, zamiast do części aplikacji, w której znajdują się fabryki.

Zarówno:

  • spróbuj pogrupować pewne parametry, które wydają się reprezentować podobne rzeczy w obiekty
  • podzielić bieżącą klasę na kilka mniejszych klas (posiadanie klasy usługi z 10 parametrami wydaje się, że klasa robi zbyt wiele rzeczy)
  • pozostaw to tak, jak jest, jeśli twoja klasa nie jest w rzeczywistości usługą, ale zamiast tego obiektem wartości

O ile obiekt, który tworzysz, nie jest klasą odpowiedzialną za przechowywanie danych, powinieneś spróbować zmienić kod i podzielić dużą klasę na mniejsze.

Andy
źródło
Cóż, logika bsiness korzysta z fabryki, która zwraca produkty, więc logika biznesowa nie widzi JSON / ptree. Ale rozumiem, o co ci chodzi, że kod Parsera w konstruktorze jest błędny.
lugge86
Klasa reprezentuje widget w graficznym interfejsie użytkownika dla systemu osadzonego, dlatego dla mnie wydaje się, że ponad 5 atrybutów: x_coord, y_coord, backgroundcolor, frameize, framecolor, tekst ...
lugge86
1
@ lugge86 Nawet jeśli używasz fabryki do parsowania JSON, a tym samym unikania wywoływania newlub konstruowania obiektów wewnątrz logiki biznesowej, nie jest to bardzo dobry projekt. Zobacz temat Nie szukaj rzeczy , który mówi Miško Hevery , który wyjaśnia bardziej szczegółowo, dlaczego wskazane przez ciebie podejście fabryczne jest złe zarówno z punktu widzenia testowania, jak i czytania. Ponadto twoja klasa wydaje się być obiektem danych, a dla tych na ogół dobrze jest mieć więcej parametrów niż zwykła klasa usług. Nie przejmowałbym się tym zbytnio.
Andy,
Nie mam nic przeciwko mojemu fabrycznemu podejściu, ale podążę za twoim linkiem i pomyślę o tym. Fabryka nie jest jednak kwestionowana w tym temacie. Pozostaje pytanie, jak skonfigurować produkty ...
lugge86,
„Posiadanie klasy usług z 10 parametrami wydaje się, że klasa robi zbyt wiele rzeczy” Nie w uczeniu maszynowym. Każdy algorytm ML miałby tony parametrów do dostrojenia. Zastanawiam się, jak należy sobie z tym poradzić przy kodowaniu ML.
Siyuan Ren,
0

Opcja 2 jest prawie słuszna.

Ulepszona opcja 2

Utwórz klasę skierowaną do przodu, której zadaniem jest wzięcie tego obiektu JSON-struktury i wybranie bitów i wywołanie konstruktora (-ów) fabryki. Bierze to, co produkuje fabryka i przekazuje klientowi.

  • Fabryka absolutnie nie ma pojęcia, że ​​istnieje coś takiego JSON.
  • Klient nie musi wiedzieć, jakich konkretnych bitów potrzebuje fabryka.

Zasadniczo „front end” mówi do 2 Bobs: „Mam do czynienia ze zredagowanymi klientami, więc inżynierowie nie muszą! Mam umiejętności ludzi!” Biedny Tom. Gdyby powiedział tylko: „Odłączam klienta od konstrukcji. To bardzo spójna fabryka”; mógł utrzymać swoją pracę.

Zbyt wiele argumentów?

Nie dla klienta - komunikacja front-end.

Front - fabryka? Jeśli nie 10 parametrów, najlepiej możesz odłożyć rozpakowywanie, jeśli nie oryginalna rzecz JSON, to trochę DTO. Czy to lepsze niż przekazanie JSON do fabryki? Tę samą różnicę mówię.

Zdecydowanie rozważyłbym przekazanie poszczególnych parametrów. Trzymaj się celu, jakim jest czysta, spójna fabryka. Unikaj obaw związanych z odpowiedzią @DavidPacker.

Łagodzenie „zbyt wielu argumentów”

  • Konstruktorzy fabryki lub klasy

    • przyjmowanie tylko argumentów dla konkretnej konstrukcji klasy / obiektu.
    • parametry domyślne
    • parametry opcjonalne
    • nazwane argumenty
  • Grupowanie argumentów interfejsu użytkownika

    • Bada, ocenia, sprawdza, sprawdza, ustawia itp. Wartości argumentów kierowane przez powyższe podpisy konstruktora.
radarbob
źródło
„Fabryka absolutnie nie ma pojęcia, że ​​istnieje coś takiego JSON” - cóż, więc po co jest fabryka? Ukrywa szczegóły tworzenia produktu zarówno przed produktem, jak i konsumentem. Dlaczego kolejna klasa powinna pomóc? Nie mam nic przeciwko fabrycznie mówiącemu JSON. Można zaimplementować kolejną fabrykę do analizowania XML i zaimplementować „Fabrykę abstrakcyjną” w przyszłości ...
lugge86
Pan Factory: „Jaki obiekt chcesz? ... Co to jest? Po prostu powiedz mi, jaki obiekt klasy zbudować.” Plik konfiguracyjny JSON jest źródłem danych, ponieważ wujek Bob mówi „to szczegół implementacji”. Może pochodzić z innego źródła i / lub w innej formie. Zasadniczo chcemy oddzielić od szczegółowych danych źródła danych. W przypadku zmiany źródła lub formularza fabryka tego nie zmieni. Biorąc pod uwagę źródło + parser, a fabryka jako oddzielone moduły sprawia, że ​​oba mogą być ponownie używane.
radarbob,