Best practice Restituzione dell'oggetto di sola lettura

11

Ho una domanda sulle "migliori pratiche" su OOP in C # (ma si applica a tutte le lingue).

Considera di avere una classe di libreria con oggetto che deve essere esposto al pubblico, ad esempio tramite accessor di proprietà, ma non vogliamo che il pubblico (le persone che usano questa classe di librerie) lo cambino.

class A
{
    // Note: List is just example, I am interested in objects in general.
    private List<string> _items = new List<string>() { "hello" }

    public List<string> Items
    {
        get
        {
            // Option A (not read-only), can be modified from outside:
            return _items;

            // Option B (sort-of read-only):
            return new List<string>( _items );

            // Option C, must change return type to ReadOnlyCollection<string>
            return new ReadOnlyCollection<string>( _items );
        }
    }
}

Ovviamente l'approccio migliore è "opzione C", ma pochissimi oggetti hanno la variante ReadOnly (e certamente nessuna classe definita dall'utente ce l'ha).

Se tu fossi l'utente della classe A, ti aspetteresti cambiamenti in

List<string> someList = ( new A() ).Items;

si propagano nell'oggetto originale (A)? O è giusto restituire un clone a condizione che sia stato scritto così nei commenti / documentazione? Penso che questo approccio al clone potrebbe portare a bug piuttosto difficili da tracciare.

Ricordo che in C ++ potevamo restituire oggetti const e si poteva solo chiamare metodi contrassegnati come const su di esso. Immagino che non ci siano tali caratteristiche / pattern nel C #? Se no, perché non dovrebbero includerlo? Credo che sia chiamato const correctness.

Ma di nuovo la mia domanda principale riguarda "cosa ti aspetti" o come gestire l'opzione A vs B.

    
posta NeverStopLearning 29.01.2013 - 15:58
fonte

4 risposte

10

Oltre alla risposta di @ Jesse, che è la soluzione migliore per le collezioni: l'idea generale è di progettare la tua interfaccia pubblica di A in modo che restituisca solo

  • tipi di valori (come int, double, struct)

  • oggetti immutabili (come "string" o classi definite dall'utente che offrono solo metodi di sola lettura, proprio come la tua classe A)

  • (solo se necessario): copie di oggetti, come dimostra la tua "Opzione B"

IEnumerable<immutable_type_here> è immutabile da solo, quindi questo è solo un caso speciale di quanto sopra.

    
risposta data 29.01.2013 - 16:33
fonte
19

Si noti inoltre che si dovrebbe programmare su interfacce e, in generale, ciò che si desidera restituire da tale proprietà è un IEnumerable<string> . Un List<string> è un po 'un no-no perché è generalmente mutabile e andrò così lontano per dire che ReadOnlyCollection<string> come implementatore di ICollection<string> sembra violare il Principio di sostituzione di Liskov.

    
risposta data 29.01.2013 - 16:11
fonte
3

Ho riscontrato un problema simile un po 'indietro e di seguito c'era la mia soluzione, ma funziona davvero solo se controlli anche la libreria:

Inizia con la tua classe A

public class A
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Quindi ricava un'interfaccia da questo con solo la porzione get delle proprietà (e qualsiasi metodo di lettura) e hai un attrezzo che

public interface IReadOnlyA
{
    public int N { get; }
}
public class A : IReadOnlyA
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Ovviamente, quando vuoi usare la versione di sola lettura, è sufficiente un cast semplice. Questo è esattamente ciò che la soluzione C è, in realtà, ma ho pensato di offrire il metodo con cui l'ho raggiunto

    
risposta data 29.01.2013 - 16:17
fonte
1

Per chiunque trovi questa domanda e sia ancora interessato alla risposta, ora c'è un'altra opzione che espone ImmutableArray<T> . Questi sono alcuni punti per cui penso che sia meglio di IEnumerable<T> o ReadOnlyCollection<T> :

  • afferma chiaramente che è immutabile (API espressiva)
  • afferma chiaramente che la raccolta è già stata formata (in altre parole non è inizializzata pigramente ed è OK per itarlo ripetutamente più volte)
  • se il conteggio degli elementi è noto, non nasconde che knowlegde come IEnumerable<T> fa
  • poiché il client non sa quale sia l'implementazione di IEnumerable o IReadOnlyCollect lui / lei non può presumere che non cambi mai (in altre parole non è garantito che gli elementi di una collezione non siano stati modificati mentre si fa riferimento a quella raccolta rimane invariata)

Anche se APi ha bisogno di notificare al client le modifiche nella collezione, esporrò un evento come membro separato.

Tieni presente che non sto dicendo che IEnumerable<T> non sia valido in generale. Lo userei ancora quando si passa la raccolta come argomento o si restituisce una raccolta inizializzata pigramente (in questo caso particolare ha senso nascondere il conteggio degli oggetti perché non è ancora noto).

    
risposta data 20.06.2018 - 10:10
fonte

Leggi altre domande sui tag