Mam tę funkcję API:
public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d,
string e, string f, out Guid code)
Nie podoba mi się to. Ponieważ kolejność parametrów staje się niepotrzebnie znacząca. Trudniej jest dodawać nowe pola. Trudniej jest zobaczyć, co się dzieje. Trudniej jest dokonać refaktoryzacji metody na mniejsze części, ponieważ powoduje to dodatkowe obciążenie związane z przekazywaniem wszystkich parametrów w podfunkcjach. Kod jest trudniejszy do odczytania.
Wpadłem na najbardziej oczywisty pomysł: mieć obiekt hermetyzujący dane i przekazywać go zamiast przekazywania każdego parametru jeden po drugim. Oto co wymyśliłem:
public class DoSomeActionParameters
{
public string A;
public string B;
public DateTime C;
public OtherEnum D;
public string E;
public string F;
}
To zredukowało moją deklarację API do:
public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)
Miły. Wygląda bardzo niewinnie, ale tak naprawdę wprowadziliśmy ogromną zmianę: wprowadziliśmy zmienność. Ponieważ to, co wcześniej robiliśmy, polegało na przekazaniu anonimowego niezmiennego obiektu: parametrów funkcji na stosie. Teraz stworzyliśmy nową klasę, która jest bardzo zmienna. Stworzyliśmy możliwość manipulowania stanem rozmówcy . To jest do bani. Teraz chcę, aby mój obiekt był niezmienny, co mam zrobić?
public class DoSomeActionParameters
{
public string A { get; private set; }
public string B { get; private set; }
public DateTime C { get; private set; }
public OtherEnum D { get; private set; }
public string E { get; private set; }
public string F { get; private set; }
public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d,
string e, string f)
{
this.A = a;
this.B = b;
// ... tears erased the text here
}
}
Jak widać, odtworzyłem mój pierwotny problem: zbyt wiele parametrów. To oczywiste, że to nie jest droga. Co ja mam zrobić? Ostatnią opcją osiągnięcia takiej niezmienności jest użycie struktury „tylko do odczytu”, takiej jak ta:
public struct DoSomeActionParameters
{
public readonly string A;
public readonly string B;
public readonly DateTime C;
public readonly OtherEnum D;
public readonly string E;
public readonly string F;
}
Pozwala to uniknąć konstruktorów ze zbyt dużą liczbą parametrów i uzyskać niezmienność. Właściwie to rozwiązuje wszystkie problemy (porządkowanie parametrów itp.). Jeszcze:
- Wszyscy (w tym FXCop i Jon Skeet) zgadzają się, że ujawnianie pól publicznych jest złe .
- Eric Lippert i wsp. Twierdzą, że poleganie na polach tylko do odczytu dla niezmienności jest kłamstwem .
Wtedy zdezorientowałem się i postanowiłem napisać to pytanie: Jaki jest najprostszy sposób w C # na uniknięcie problemu „zbyt wielu parametrów” bez wprowadzania zmienności? Czy można użyć do tego celu struktury tylko do odczytu, a jednocześnie nie mieć złego projektu API?
OBJAŚNIENIA:
- Proszę założyć, że nie doszło do naruszenia zasady odpowiedzialności pojedynczej. W moim oryginalnym przypadku funkcja po prostu zapisuje podane parametry do pojedynczego rekordu DB.
- Nie szukam konkretnego rozwiązania dla danej funkcji. Szukam uogólnionego podejścia do takich problemów. Szczególnie interesuje mnie rozwiązanie problemu „zbyt wielu parametrów” bez wprowadzania zmienności lub okropnego projektu.
AKTUALIZACJA
Podane tutaj odpowiedzi mają różne zalety / wady. Dlatego chciałbym przekształcić to w wiki społeczności. Myślę, że każda odpowiedź z próbką kodu i zaletami / wadami byłaby dobrym przewodnikiem po podobnych problemach w przyszłości. Teraz próbuję dowiedzieć się, jak to zrobić.
DoSomeActionParameters
jest to obiekt jednorazowy, który zostanie odrzucony po wywołaniu metody.Odpowiedzi:
Jeden styl przyjęty we frameworkach jest zwykle podobny do grupowania powiązanych parametrów w powiązane klasy (ale po raz kolejny problematyczny jest zmienność):
źródło
Użyj połączenia konstruktora i interfejsu API w stylu języka specyficznego dla domeny - Fluent Interface. Interfejs API jest nieco bardziej szczegółowy, ale dzięki funkcji Intellisense bardzo szybko się pisze i jest łatwy do zrozumienia.
źródło
DoSomeAction
była to metoda.To, co masz, jest całkiem pewnym wskazaniem, że dana klasa narusza zasadę pojedynczej odpowiedzialności, ponieważ ma zbyt wiele zależności. Poszukaj sposobów na refaktoryzację tych zależności w klastry zależności fasady .
źródło
Po prostu zmień strukturę danych parametrów z a
class
na astruct
i gotowe.Metoda otrzyma teraz własną kopię struktury. Zmiany dokonane w zmiennej argumentu nie mogą być obserwowane przez metodę, a zmiany wprowadzone przez metodę w zmiennej nie mogą być obserwowane przez obiekt wywołujący. Izolację osiąga się bez niezmienności.
Plusy:
Cons:
źródło
Co powiesz na utworzenie klasy konstruktora w klasie danych. Klasa danych będzie miała wszystkie ustawiacze jako prywatne i tylko twórca będzie mógł je ustawić.
źródło
DoSomeActionParameters.Builder
instancja może zostać użyta do utworzenia i skonfigurowania dokładnie jednejDoSomeActionParameters
instancji. Po wywołaniuBuild()
kolejnych zmianBuilder
właściwości będzie kontynuował modyfikowanie właściwości oryginalnejDoSomeActionParameters
instancji, a kolejne wywołaniaBuild()
będą nadal zwracały tę samąDoSomeActionParameters
instancję. Naprawdę powinnopublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
.Nie jestem programistą C #, ale uważam, że C # obsługuje nazwane argumenty: (F # to robi, a C # jest w dużej mierze kompatybilny z funkcjami tego rodzaju). http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342
Więc wywołanie oryginalnego kodu staje się:
nie zajmuje to więcej miejsca / myśli, że tworzenie obiektu jest tworzone i ma wszystkie zalety, z faktu, że w ogóle nie zmieniłeś tego, co dzieje się w nieokreślonym systemie. Nie musisz nawet niczego przekodowywać, aby wskazać, że argumenty są nazwane
Edycja: oto artykuł, który znalazłem na ten temat. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ Powinienem wspomnieć, że C # 4.0 obsługuje nazwane argumenty, 3.0 nie
źródło
Dlaczego po prostu nie stworzyć interfejsu, który wymusza niezmienność (tj. Tylko pobierające)?
Zasadniczo jest to pierwsze rozwiązanie, ale wymuszasz na funkcji użycie interfejsu w celu uzyskania dostępu do parametru.
a deklaracja funkcji staje się:
Plusy:
struct
rozwiązanieCons:
DoSomeActionParameters
jest to klasa, na którą można zmapowaćIDoSomeActionParameters
źródło
IDoSomeActionParameters
i chce uchwycić zawarte w niej wartości, nie ma możliwości dowiedzenia się, czy trzymanie odwołania będzie wystarczające, czy też musi skopiować wartości do innego obiektu. Czytelne interfejsy są przydatne w niektórych kontekstach, ale nie jako sposób na uczynienie rzeczy niezmiennymi.Wiem, że to stare pytanie, ale pomyślałem, że zgodzę się z moją sugestią, ponieważ właśnie musiałem rozwiązać ten sam problem. Wprawdzie mój problem był nieco inny niż twój, ponieważ miałem dodatkowe wymaganie, aby nie chcieć, aby użytkownicy mogli samodzielnie skonstruować ten obiekt (całe nawodnienie danych pochodziło z bazy danych, więc mogłem uwięzić całą konstrukcję wewnętrznie). To pozwoliło mi użyć prywatnego konstruktora i następującego wzorca;
źródło
Możesz zastosować podejście w stylu Konstruktora, choć w zależności od złożoności
DoSomeAction
metody może to być dotkliwe. Coś w tym stylu:źródło
Oprócz odpowiedzi manji - możesz też chcieć podzielić jedną operację na kilka mniejszych. Porównać:
i
Dla tych, którzy nie znają POSIX, stworzenie dziecka może być tak proste, jak:
Każdy wybór projektu stanowi kompromis dotyczący tego, jakie operacje może wykonać.
PS. Tak - jest podobny do budowniczego - tylko odwrotnie (tj. Po stronie odbiorcy zamiast dzwoniącego). W tym konkretnym przypadku może być lepsze niż builder.
źródło
tutaj jest trochę inny niż Mikeys, ale staram się, aby całość była jak najmniejsza do napisania
Obiekt DoSomeActionParameters jest niezmienny, ponieważ może być i nie można go utworzyć bezpośrednio, ponieważ jego domyślny konstruktor jest prywatny
Inicjator nie jest niezmienny, a jedynie transport
Użycie korzysta z inicjalizatora w inicjatorze (jeśli dostaniesz mój dryf) I mogę mieć wartości domyślne w domyślnym konstruktorze inicjatora
Parametry będą tutaj opcjonalne, jeśli chcesz, aby niektóre były wymagane, możesz je umieścić w domyślnym konstruktorze inicjatora
A walidacja może przejść w metodzie Create
Więc teraz wygląda na to
Wciąż trochę kooki, wiem, ale i tak spróbuję
Edycja: przeniesienie metody create do statycznej w obiekcie parametrów i dodanie delegata, który przekazuje inicjalizator, usuwa część funkcjonalności z wywołania
Więc teraz rozmowa wygląda tak
źródło
Użyj struktury, ale zamiast pól publicznych, mają właściwości publiczne:
Jon i FXCop będą usatysfakcjonowani, ponieważ ujawniasz właściwości, a nie pola.
Eric będzie usatysfakcjonowany, ponieważ korzystając z właściwości, możesz upewnić się, że wartość jest ustawiona tylko raz.
Jeden pół-niezmienny obiekt (wartość można ustawić, ale nie można jej zmienić). Działa dla typów wartości i referencji.
źródło
this
ulegają mutacji, są znacznie gorsze niż pola publiczne. Nie jestem pewien, dlaczego uważasz, że ustawienie wartości tylko raz sprawia, że wszystko jest „w porządku”; jeślithis
zaczniemy od domyślnego wystąpienia struktury, problemy związane z -mutating properties będą nadal istnieć.Wariant odpowiedzi Samuela , którego użyłem w swoim projekcie, gdy miałem ten sam problem:
I do użycia:
W moim przypadku parametry były celowo modyfikowalne, ponieważ metody ustawiające nie pozwalały na wszystkie możliwe kombinacje, a jedynie ujawniały ich wspólne kombinacje. To dlatego, że niektóre z moich parametrów były dość złożone, a pisanie metod dla wszystkich możliwych przypadków byłoby trudne i niepotrzebne (rzadko używane są szalone kombinacje).
źródło
DoMagic
Posiada w swojej umowie jednak, że nie będzie modyfikować obiekt. Ale chyba nie chroni to przed przypadkowymi modyfikacjami.