Sovraccarico del costruttore o permesso nullo?

3

Qual è il design preferito da utilizzare, un costruttore che consente null o due costruttori in cui uno lancia una ArgumentNullException su null?

Due costruttori con lancio di eccezioni

public class Foo
{
    private IDictionary<string, string> _bar;

    public Foo()
    {
        _bar = new Dictionary<string, string>();
    }

    public Foo(IDictionary<string, string> bar)
    {
        if (bar == null) { throw new ArgumentNullException(); }
        _bar = bar;
    }
}

Un costruttore che gestisce null

public class Foo
{
    private IDictionary<string, string> _bar;

    public Foo(IDictionary<string, string> bar = null)
    {
        if (bar == null) {
            _bar = new Dictionary<string, string>();
        } else {
            _bar = bar;
        }
    }
}
    
posta Fred 07.04.2016 - 13:28
fonte

5 risposte

2

Dovrebbe essere fornita l'interfaccia più semplice che copra tutti i casi che risultano in uno stato valido alla costruzione del tuo oggetto. Dovresti essere anche liberale in ciò che accetti. Fornire un costruttore è sempre più semplice rispetto a fornire due, e gli scrittori di codice che utilizzano questa classe non saranno gravati da una decisione aggiuntiva.

Se _bar è esposto in qualche modo dagli altri metodi / proprietà della tua classe, documenta il costruttore con i commenti della documentazione su quale sia il comportamento quando viene passato null come argomento al tuo singolo costruttore (cioè quella barra è vuota di default se viene passato nulla in).

    
risposta data 07.04.2016 - 14:11
fonte
2

Consiglierei di usare un metodo di fabbrica. Puoi indicare la tua intenzione ( createEmpty() , createWithValues() ) ed esporre ciò che ti aspetti dal chiamante: createWithValues() - null non è consentito. Il costruttore sarebbe quindi privato e solo il createWithValues() conterrà il controllo Null o qualsiasi altro controllo del valore.

class Foo {
    private Map<String, String> bar;
    private Foo(final Map<String, String> bar) {
       this.bar = bar;
    }

    public static Foo createEmpty() {
       return new Foo(new HashMap<>());
    } 

    public static Foo createWithValue(final Map<String, String> values) {
       if (values == null) throw new YourFavoriteExceptionForThisCase();
       return new Foo(values)
    }
}

Semplice e pulito. (Spiacente, il codice è in java)

    
risposta data 07.04.2016 - 14:40
fonte
0

Sebbene l'uso di null per indicare un valore predefinito sia un principio ben stabilito che riconduce alle radici C / C ++ di C #, un altro approccio potrebbe essere quello di prendere in prestito i concetti di Option type (chiamato Maybe in Haskell)

Questo costringerà il chiamante a specificare esplicitamente se vuole o meno il valore predefinito. Ciò evita la confusione relativa al passaggio deliberato di null come parametro errato e utilizzando null per indicare facoltativo:

Avresti quindi un singolo costruttore che copre ENTRAMBI i dubbi del tuo codice "due costruttori".

public Foo(Option<IDictionary<string, string>> optionalBar)
{
    if (bar == null) 
       throw ArgumentNullException();
    if (bar.HasValue) {
        _bar = new Dictionary<string, string>();
    } else {
        _bar = bar.Value;
    }
}

Sfortunatamente, C # non scorre abbastanza bene come dice un linguaggio FP, che userebbe la corrispondenza dei pattern per ragionare su Some<Dictionary> e None .

    
risposta data 07.04.2016 - 14:07
fonte
0

Sono per il primo caso.

Nel secondo caso, promuove null come parametro valido. IMO null non deve essere utilizzato per indicare l'assenza di valore. Ecco perché esistono tipi Option (come suggerito da StuartLC).

    
risposta data 07.04.2016 - 14:24
fonte
0

Non sono enormemente spiazzato da nessuno dei due per essere onesto.

Il primo può lanciare un'eccezione imprevista e il secondo inizializza una variabile membro che non è stata nemmeno fornita come parametro. I costruttori non devono arrecare un comportamento inaspettato.

Preferirei qualcosa del genere:

public class Foo
{
    private IDictionary<string, string> _bar;

    private IDictionary<string, string> Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Dictionary<string, string>();

            return _bar;
        }

        set { _bar = value; }

    }

    public Foo()
    {

    }

    public Foo(IDictionary<string, string> bar)
    {
        Bar = bar;
    }
}

L'utente è libero di fornire un parametro o chiamare il costruttore senza parametri. E se per qualche svista, la proprietà viene chiamata senza essere inizializzata, ne crea uno al volo tramite lazy instantiation.

    
risposta data 07.04.2016 - 14:52
fonte