Czy można mieć klasę tylko z właściwościami do celów refaktoryzacji?

79

Mam metodę, która przyjmuje 30 parametrów. Wziąłem parametry i umieściłem je w jednej klasie, tak że mogłem po prostu przekazać jeden parametr (klasę) do metody. Czy w przypadku refaktoryzacji jest całkowicie w porządku, aby przekazać obiekt, który zawiera wszystkie parametry, nawet jeśli to wszystko, co zawiera.

Xaisoft
źródło
Poniższe odpowiedzi są dobre. W tym celu zawsze zakładałem klasę. Czy używasz właściwości automatycznych? msdn.microsoft.com/en-us/library/bb384054.aspx
Harv
Inaczej nazywany typem POD (zwykłe stare dane).
nowy123456
17
Chociaż wiele z udzielonych odpowiedzi jest świetnych i rzeczywiście dobrym pomysłem jest refaktoryzacja tych parametrów na coś nieco łatwiejszego w obsłudze, powiedziałbym, że fakt, że ta metoda wymaga 30 różnych elementów danych, jest znakiem że robi za dużo. Ale z drugiej strony nie widzieliśmy tej metody ani nawet nie znamy jej celu.
użytkownik
1
Więc ta metoda, która pobierała wszystkie parametry, czy przeniosłaś ją do nowej klasy? A może stara metoda przyjmuje nową klasę jako pojedynczy parametr? Spojrzałbym na umieszczenie metody w nowej klasie, jeśli ma to sens. Klasy zawierające tylko dane i bez metod nazywane są klasami anemicznymi i wydaje się, że jest to trochę mniej OO. Nie jest to jednak twarda i szybka zasada, po prostu coś, na co warto zwrócić uwagę.
Kurt
@Michael: Zgadzam się z tobą. Kolejna kwestia - uważam, że nie powinieneś tworzyć obiektu dedykowanego metodzie, chyba że jego wewnętrzna relacja jest poza metodą.
Saturn Technologies,

Odpowiedzi:

75

To jest świetny pomysł. Jest to zwykle sposób, w jaki kontrakty danych są wykonywane na przykład w programie WCF.

Jedną z zalet tego modelu jest to, że jeśli dodasz nowy parametr, konsument klasy nie musi zmieniać tylko w celu dodania parametru.

Jak wspomina David Heffernan, może pomóc samodzielnie udokumentować kod:

FrobRequest frobRequest = new FrobRequest
{
    FrobTarget = "Joe",
    Url = new Uri("http://example.com"),
    Count = 42,
};
FrobResult frobResult = Frob(frobRequest);
McKay
źródło
Świetny. Miałem wrażenie, że samo posiadanie klasy z właściwościami i bez metod jest bezużyteczne.
Xaisoft
2
Jeśli będzie zawierał tylko członków danych, wolę nadać mu strukturę. Używanie struct sprawia wrażenie, że to tylko dane.
Yousf
28
użycie struktury ma efekty uboczne, takie jak kopiowanie podczas przekazywania semantyki. Upewnij się, że jesteś tego świadomy przed podjęciem takiej decyzji
Adrian Zanescu
Tak zazwyczaj pisze się moduły javascript i wydaje się, że jest to najlepsza metoda akceptowania mniej lub bardziej parametrów w miarę dojrzewania modułu. someFunction ({myParam1: 'something', myParam2: 'somethingElse'});
Kristian
63

Podczas gdy inne odpowiedzi tutaj poprawnie wskazują, że przekazanie wystąpienia klasy jest lepsze niż przekazanie 30 parametrów, należy pamiętać, że duża liczba parametrów może być symptomem podstawowego problemu.

Np. Wiele razy metody statyczne zwiększają liczbę parametrów, ponieważ powinny one być przez cały czas metodami instancji, a Ty przekazujesz wiele informacji, które można by łatwiej utrzymać w instancji tej klasy.

Alternatywnie poszukaj sposobów grupowania parametrów w obiekty o wyższym poziomie abstrakcji. Zrzucenie zestawu niepowiązanych parametrów do jednej klasy to rozwiązanie IMO ostatniej szansy.

Zobacz Ile parametrów to za dużo? po więcej pomysłów na ten temat.

D'Arcy Rittich
źródło
W jakim sensie te parametry nie są ze sobą powiązane? Wszystkie są używane w tej metodzie. To bardzo silny związek. Co więcej, ogólnie preferowane jest podanie parametrów na stosie. Pomyśl o wielowątkowości.
David Heffernan
12
Nie można na to odpowiedzieć bez zobaczenia kodu OP, ale nie zgadzam się, że po prostu dlatego, że dwie wartości są używane razem w metodzie, mają one silny związek. Gdyby to była prawda, projekt OO oznaczałby w końcu stworzenie jednej dużej klasy, która przechowuje wszystkie możliwe właściwości używane w aplikacji.
D'Arcy Rittich
Nie, masz rację, niekoniecznie powiązane. Ale też niekoniecznie niepowiązane. Tak więc, jak mówisz, dla pewności musiałby zobaczyć kod.
David Heffernan
23
30 parametrów? Zadowoliłbym się najprawdopodobniej niepowiązanym, a także prawdopodobnie wskazującym na metodę, która jest zbyt długa, ma dużą złożoność cyklomatyczną i jest rajem dla błędów. Weź pod uwagę, że mówimy o metodzie, której zachowanie ma co najmniej 30 wymiarów, w których może się różnić. Jeśli istnieją testy jednostkowe dla tej metody, nie chciałbym być tym, który napisze je TBH.
Steve Rowbotham
3
Innym prawdopodobnym scenariuszem jest to, że wszystkie parametry są powiązane, ale są źle zorganizowane. Jest całkiem prawdopodobne, że grupy parametrów powinny być grupowane w osobne obiekty, a nie przekazywane po kawałku. Powiedzmy, że mam metodę, która operuje na informacjach takich jak „osoba”, „adres”, „recepta”… które z łatwością mogłyby wynieść 30 parametrów, gdyby każda z dyskretnych informacji została przekazana do mojej metody oddzielnie.
Nate CK
25

To dobry początek. Ale teraz, gdy masz już tę nową klasę, rozważ obrócenie kodu na lewą stronę. Przenieś metodę, która przyjmuje tę klasę jako parametr do nowej klasy (oczywiście przekazując instancję oryginalnej klasy jako parametr). Teraz masz dużą metodę, samą w klasie, i łatwiej będzie ją podzielić na mniejsze, łatwiejsze w zarządzaniu i testowalne metody. Niektóre z tych metod mogą powrócić do oryginalnej klasy, ale spora część prawdopodobnie pozostanie w nowej klasie. Przeszedłeś dalej niż wprowadzenie obiektu parametru, aby zamienić metodę na obiekt metody .

Posiadanie metody z trzydziestoma parametrami to dość silny znak, że metoda jest zbyt długa i zbyt skomplikowana. Zbyt trudne do debugowania, zbyt trudne do przetestowania. Powinieneś więc coś z tym zrobić, a Wprowadzenie obiektu parametru to dobre miejsce na początek.

Carl Manaster
źródło
4
To jest ABSOLUTNIE WAŻNE! Tworzenie tych pakietów atrybutów jest świetne tylko dlatego, że jest pierwszym krokiem do tworzenia nowych klas z własnymi metodami, a prawie zawsze znajdziesz metody, które należą do twoich nowych klas - szukaj ich! Myślę, że to najbardziej krytyczna odpowiedź w tej grupie, powinna znajdować się blisko szczytu.
Bill K
15

Chociaż refaktoryzacja do obiektu parametru nie jest sama w sobie złym pomysłem, nie powinna być używana do ukrycia problemu, że klasa, która potrzebuje 30 elementów danych dostarczonych z innego miejsca, może nadal pachnieć kodem. Refaktoryzację obiektu parametru wprowadzenia należy prawdopodobnie traktować raczej jako krok na drodze w szerszym procesie refaktoryzacji niż jako koniec tej procedury.

Jedną z obaw, których tak naprawdę nie rozwiązuje, jest Feature Envy. Czy fakt, że klasa, do której przekazywany jest obiekt parametrów, jest tak zainteresowana danymi innej klasy, nie wskazuje na to, że może metody działające na tych danych powinny zostać przeniesione do miejsca, w którym te dane się znajdują? Naprawdę lepiej jest zidentyfikować klastry metod i danych, które należą do siebie i pogrupować je w klasy, zwiększając w ten sposób hermetyzację i uelastyczniając kod.

Po kilku iteracjach rozdzielania zachowania i danych, na których działa, na oddzielne jednostki, powinieneś zauważyć, że nie masz już żadnych klas z ogromną liczbą zależności, co zawsze jest lepszym wynikiem końcowym, ponieważ uczyni twój kod bardziej elastycznym.

Steve Rowbotham
źródło
10

To doskonały pomysł i bardzo powszechne rozwiązanie problemu. Metody z więcej niż 2 lub 3 parametrami stają się wykładniczo coraz trudniejsze do zrozumienia.

Zamknięcie tego wszystkiego w jednej klasie sprawia, że ​​kod jest znacznie bardziej przejrzysty. Ponieważ Twoje właściwości mają nazwy, możesz napisać samodokumentujący kod w następujący sposób:

params.height = 42;
params.width = 666;
obj.doSomething(params);

Oczywiście, gdy masz wiele parametrów, alternatywa oparta na identyfikacji pozycyjnej jest po prostu okropna.

Kolejną korzyścią jest to, że dodawanie dodatkowych parametrów do kontraktu interfejsu można wykonać bez wymuszania zmian we wszystkich lokalizacjach połączeń. Jednak nie zawsze jest to tak trywialne, jak się wydaje. Jeśli różne miejsca wywołań wymagają różnych wartości nowego parametru, trudniej jest je znaleźć niż w przypadku podejścia opartego na parametrach. W podejściu opartym na parametrach dodanie nowego parametru wymusza zmianę w każdym miejscu wywołania w celu dostarczenia nowego parametru i można pozwolić kompilatorowi na znalezienie ich wszystkich.

David Heffernan
źródło
9

Martin Fowler w swojej książce Refaktoryzacja nazywa ten obiekt „ Wprowadzanie parametrów” . Mając to na uwadze, niewielu nazwałoby to złym pomysłem.

Austin Salonen
źródło
1
Tak, ale dla 30 parametrów coś jest nie tak gdzie indziej, co należy najpierw naprawić
manojlds
5

30 parametrów to bałagan. Myślę, że o wiele ładniej jest mieć klasę z właściwościami. Można nawet utworzyć wiele „klas parametrów” dla grup parametrów pasujących do tej samej kategorii.

C.Evenhuis
źródło
3

Możesz również rozważyć użycie struktury zamiast klasy.

Ale to, co próbujesz zrobić, jest bardzo powszechne i świetny pomysł!

Tad Donaghe
źródło
Właściwie myślałem o użyciu struktury, ale byłem ciekawy, czy użycie struktury może mieć jakieś wady.
Xaisoft
Dlaczego David? Co ma z tym wspólnego liczba pól? Po prostu ciekawy.
Tad Donaghe
2
Ze względu na wydajność. Chociaż semantycznie bardziej sensowne jest użycie struktury tutaj, ponieważ liczy się tylko wartość każdego pola, a tożsamość odniesienia jest nieistotna, około 30 pól jest bardziej do skopiowania (powiedzmy, że są to głównie typy referencyjne; 120 bajtów w przypadku wersji 32-bitowej i 240 bajtów w przypadku 64) niż z klasą (4 lub 8 bajtów). Jednak charakter struktury kopiowania według wartości oznacza, że ​​po skopiowaniu przez dostęp będzie szybszy niż w przypadku typu referencyjnego, więc próg, w którym struktura jest mniej wydajna, jest wyższy niż 1-wskaźnikowy rozmiar, który pociąga za sobą powyższe. Przy> 4 rozmiarach wskaźnika czas to rozważyć.
Jon Hanna
Niesamowite. Dziękuję za wyjaśnienie! :)
Tad Donaghe
3

Może być rozsądne użycie klasy zwykłych starych danych, niezależnie od tego, czy dokonujesz refaktoryzacji, czy nie. Ciekawi mnie, dlaczego pomyślałeś, że tak nie jest.

Jon Hanna
źródło
Nie pamiętam dokładnie, ale myślę, że kiedy wspomniałem gdzieś tutaj jakiś czas temu, że tworzę klasę tylko z właściwościami, wtedy była to pogardzana. Po drugie, czy mówisz, że tylko parametry, które się nie zmieniają, powinny być przekazywane w metodzie, innymi słowy, jeśli metoda zmienia parametr, nie należy go przekazywać jako parametr.
Xaisoft
Klasy, które są tylko właściwościami, mogą mieć nieprzyjemny zapach (patrz odpowiedź Steve'a Rowbothama), który oznacza coś, co powinno być klasą „pełną”. Dobrym znakiem jest sytuacja, gdy używasz podobnych klas więcej niż raz. Jednak nie zawsze tak jest, a przy podrzucaniu między klasą 30 pól a metodą 30-parametrową można oprzeć się na tej pierwszej. Poza tym może to być początek budowania tej „pełnej” klasy.
Jon Hanna
... Usuwam mój komentarz dotyczący niezmienności, ponieważ bardziej myślałem o klasie tej będącej tylko metodą publiczną (gdzie obiekt „request” opisuje intencje wywołującego). Tutaj zmiana „prośby” może prowadzić do nieporozumień. W jednorazowym przypadku wygodniej jest być modyfikowalnym i budować na bieżąco. Niezmienność oznaczałaby po prostu zastąpienie 30-argumentowego wywołania 30-argumentowym konstruktorem, po którym nastąpi wywołanie, więc nie będzie wygranej.
Jon Hanna
3

Może opcjonalne i nazwane parametry C # 4.0 będą dobrą alternatywą?

W każdym razie metoda, którą opisujesz, może być również dobra do abstrakcji zachowania programów. Na przykład możesz mieć jedną standardową SaveImage(ImageSaveParameters saveParams)funkcję w interfejsie, która ImageSaveParametersrównież jest interfejsem i może mieć dodatkowe parametry w zależności od formatu obrazu. Na przykład JpegSaveParametersma Quality-właściwość, podczas gdy PngSaveParametersmaBitDepth -właściwość.

W ten sposób robi to okno dialogowe zapisywania w Paint.NET, więc jest to bardzo realny przykład.

loog
źródło
3

Jak wspomniano wcześniej: jest to właściwy krok, ale weź pod uwagę również następujące kwestie:

  • Twoja metoda może być zbyt złożona (powinieneś rozważyć podzielenie jej na więcej metod lub nawet przekształcenie jej w osobną klasę)
  • jeśli tworzysz klasę dla parametrów, zrób to niezmienną
  • jeśli wiele parametrów może mieć wartość null lub jakąś wartość domyślną, możesz chcieć użyć wzorca konstruktora dla swojej klasy.
Dostać
źródło
0

Tyle świetnych odpowiedzi. Chciałbym dodać moje dwa centy.

Obiekt parametru to dobry początek. Ale można zrobić więcej. Rozważ następujące (przykłady w języku Ruby):

/ 1 / Zamiast po prostu grupować wszystkie parametry, sprawdź, czy może istnieć sensowne grupowanie parametrów. Możesz potrzebować więcej niż jednego obiektu parametrów.

def display_line(startPoint, endPoint, option1, option2)

może stać się

def display_line(line, display_options)

/ 2 / Obiekt parametru może mieć mniejszą liczbę właściwości niż pierwotna liczba parametrów.

def double_click?(cursor_location1, control1, cursor_location2, control2)

może stać się

def double_click?(first_click_info, second_click_info) 
                       # MouseClickInfo being the parameter object type 
                       # having cursor_location and control_at_click as properties

Takie zastosowania pomogą Ci odkryć możliwości dodania znaczącego zachowania do tych obiektów parametrów. Przekonasz się, że wcześniej otrząsną się z początkowego zapachu klasy danych, aby zapewnić Ci wygodę. : -)

rpattabi
źródło