Metoda z wieloma parametrami

Czasami metody mają zbyt dużo parametrów przez co wywołanie ich jest niewygodne i może zajmować nawet dwie linie. Oczywiście pierwsza rzecz, którą powinniśmy zrobić jest sprawdzenie czy metoda czasami nie wykonuje zbyt wielu operacji tzn. przestrzega zasadę Single Responsibility.  Jeśli mamy pewność, że metoda przestrzega wszelkie zasady i jest dobrze zaprojektowana wtedy trzeba pomyśleć jak zmniejszyć liczbę parametrów. W poście przedstawię kilka prób uzyskania takiego efektu.

Logiczne wydaje się, utworzenie kontenera, który zawiera wszelkie potrzebne informacje do wykonania takiego zadania. Możemy zacząć od:

class FetchDataArgs
{
    public int Id { get; set; }
    public int Offset { get; set; }
    public int Limit { get; set; }
    public DateTime From { get; set; }
    public DateTime To { get; set; }
    // inne parametry
}
class Loader
{
    public void LoadData(FetchDataArgs fetchDataArgs)
    {        
    }
}

LoadData zamiast przyjmować wiele parametrów przyjmuje jeden kontener FetchDataArgs. Oczywiście ma to sens tylko w sytuacjach gdy jest takich parametrów np. siedem. Rozwiązanie działa i można teraz wykonać LoadData następująco:

FetchDataArgs fetchDataArgs=new FetchDataArgs();
fetchDataArgs.To = DateTime.Now;
fetchDataArgs.From = DateTime.Now;
// ...

Loader loader=new Loader();
loader.LoadData(fetchDataArgs);

Podejście jednak niezbyt eleganckie. Główną wadą jest możliwość modyfikowania FetchDataArgs w metodzie LoadData. Oznacza to, że wywołana metoda może modyfikować stan obiektu znajdującej się po za nią tzn.:

class Loader
{
    public void LoadData(FetchDataArgs fetchDataArgs)
    {
        fetchDataArgs.From = DateTime.Now;
    }
}

Metoda powinna mieć dostęp tylko do odczytu jeśli chodzi o parametry wejściowe. Metoda wywołana nie może zmieniać stanu metody wywołującej. Można tego uniknąć poprzez zdefiniowanie prywatnych setterow:

class FetchDataArgs
{
    public int Id { get; private set; }
    public int Offset { get; private set; }
    public int Limit { get; private set; }
    public DateTime From { get; private set; }
    public DateTime To { get; private set; }
    // inne parametry
}

Niestety aby takie pola zainicjalizować należałoby zdefiniować konstruktor, który będzie miał znów dużo parametrów (a tego chcemy uniknąć).

Dobry rozwiązaniem jest zdefiniowanie struktury:

struct FetchDataArgs
{
    public int Id;
    public int Offset;
    public int Limit;
    public DateTime From;
    public DateTime To;
    // inne parametry
}

Co takie rozwiązanie gwarantuje? Przekazanie struktury jako parametr spowoduje wykonanie jej kopii a co za tym idzie, niemożliwe będzie modyfikowanie stanu metody wywołującej.

Inne sprytne ale chyba zbyt skomplikowane rozwiązanie to zdefiniowanie interfejsu wyłącznie z getterami tzn.:

internal interface IFetchDataArgs
{
    int Id { get; }
    int Offset { get; }
    int Limit { get; }
    DateTime From { get; }
    DateTime To { get; }
}

class FetchDataArgs : IFetchDataArgs
{
    public int Id { get; set; }
    public int Offset { get; set; }
    public int Limit { get; set; }
    public DateTime From { get; set; }
    public DateTime To { get; set; }
    // inne parametry
}
class Loader
{
    public void LoadData(IFetchDataArgs fetchDataArgs)
    {
        fetchDataArgs.From = DateTime.Now; // brak settera - niemozliwe
    }
}

Metoda wywołująca tworzy FetchDataArgs zatem ma do do dyspozycji settery. Z kolei LoadData operuje na interfejsie zatem nie może zmodyfikować jego stanu (brak setterow w interfejsie). Oczywiście użytkownik mógłby zrzutować interfejs na obiekt i zmienić jego stan ale to jest już złą praktyką.

Leave a Reply

Your email address will not be published.