Qual è il modo migliore per accedere a più proprietà figlio?

6

Ho una situazione in cui devo accedere a diverse "sotto-proprietà" di un oggetto e trovo piuttosto sconveniente scrivere questo codice.

Mi stavo chiedendo come affrontare al meglio questa situazione:

void Main()
{
    MyAClass aClass = new MyAClass();
    aClass.BClass.CClass.DoSomething();
}

Oltre ad essere un pugno nell'occhio, non ci sono controlli nulli, e se uno li dovesse fare, il codice sembrerebbe ancora più disordinato.

Al momento creo le scorciatoie, ma sono anche un po 'di odore di codice:

public class MyAClass
{
    public MyBClass BClass { get; set; }

    public void DoSomething()
    {
        if (BClass == null || BClass.CClass == null)
            throw new Exception();

        BClass.CClass.DoSomething();
    }
}

Qual è il modo migliore per accedere a più proprietà come questa?

    
posta André Hauptfleisch 18.01.2012 - 16:32
fonte

4 risposte

12

Il primo approccio viola la Legge di Demeter e Dì, non chiedere .

Il punto di OOP non è quello di essere carina, ma di avere un modo strong e semplice di raggiungere la modularità. I moduli sono il mezzo per applicare la divisione e la conquista allo sviluppo del software stesso, dove la conquista viene raggiunta attraverso l'implementazione e la divisione viene raggiunta attraverso interfacce. Le interfacce sono tutto ciò che conta davvero.

A nessuno importa davvero come implementa qualcosa, a patto che sia corretto e ragionevolmente veloce. Ciò che è molto più importante è la sua praticità. Aspettarsi che gli utenti del tuo codice esplorino i tuoi oggetti grafici non è utile per loro. Qualsiasi componente tu colleghi per raggiungere il tuo obiettivo è il tuo business.

Un utente del tuo codice vuole solo DoSomething e la tua classe gli permette di farlo. Questo è il tuo contratto. E l'istituzione (e la difesa) di tale contratto aumenta la robustezza, perché sei libero di cambiare il modo in cui realizzi quel qualcosa senza violare alcun codice esterno.

    
risposta data 18.01.2012 - 18:32
fonte
5

Questo è davvero un problema di progettazione. Il chiamante deve avere conoscenza (cioè "una dipendenza") sul lato interno di MyAClass - e in questo caso, non solo sul lato interno di MyAClass, ma anche internals dell'istanza MyBClass di MyAClass, l'istanza di MyCClass che BClass detiene, ecc. Ci sono diversi problemi con questo design - in particolare, sarà fragile e difficile da modificare (ad esempio, le modifiche a MyCClass potrebbero avere un impatto non solo su questo chiamante, ma anche su MyAClass, MyBClass, ecc.) E avere un problema di proprietà / ordine di istanziazione (cioè, come si menziona, "nessun controllo nullo") - questa API richiede che ciascuna delle classi abbia un'istanza non nulla di ogni oggetto già inizializzato, o peggio ancora, la proprietà "getter" eseguirà un controllo Null e quindi istanzia uno prima di restituire il nuovo oggetto. Questo è davvero pessimo perché ciò che assomiglia a una semplice chiamata "get" di una proprietà, potrebbe causare un'enorme allocazione o operazione di memoria.

Per farla breve, se il chiamante ha davvero bisogno di avere conoscenza di queste altre classi, forse è possibile ridisegnare l'API, in modo che il chiamante allochi le istanze di ciascuno degli oggetti (sia da un esplicito "nuovo" o tramite una fabbrica ) e li passa a MyAClass come argomenti del costruttore, rendendo effettivamente il chiamante il "proprietario".

Per quanto riguarda le immagini grandi, ti consiglio di leggere un ottimo libro che ho trovato di recente - API " Design per C ++ " di Martin Reddy - è incentrato sul C ++, ma i problemi di progettazione sono in gran parte gli stessi. Puoi anche consultare "C # efficace: 50 modi specifici per migliorare il tuo C #" di Bill Wagner e "C # più efficace: 50 modi specifici per migliorare il tuo C #" di Bill Wagner per alcuni utili suggerimenti sulla progettazione dell'API .NET.

    
risposta data 18.01.2012 - 17:49
fonte
3

Trascurando il fatto che questo è un problema di progettazione, come è stato menzionato nelle altre risposte, c'è un modo per migliorare un po 'quel codice in C # quando non puoi aggiungere tu stesso il metodo. Questo è particolarmente utile quando devi attraversare gerarchie ancora più lunghe.

void Main()
{
    MyAClass myAClass = new MyAClass();
    MyCClass inner = myAClass
        .IfNotNull( a => a.BClass )
        .IfNotNull( b => b.CClass );
    if ( inner != null )
    {
        inner.DoSomething();
    }
}

Il codice per il metodo di estensione IfNotNull è:

public static TInner IfNotNull<T, TInner>(
    this T source,
    Func<T, TInner> selector )
    where T : class
{
    return source != null ? selector( source ) : default( TInner );
}

Naturalmente in uno scenario in cui si genererebbe comunque un'eccezione e sarebbe un'eccezione per incontrare un oggetto che è nullo lungo il percorso, si potrebbe semplicemente prendere il NullReferenceException e agire su che in qualsiasi modo tu voglia.

    
risposta data 18.01.2012 - 21:28
fonte
2

Hai ragione, è un odore di codice e un potenziale problema (se un bambino in catena è nullo).

Per evitare questo dovresti seguire la regola del livello di accesso al codice - Non ricordo bene l'affermazione, ma il fatto è:

Your method must do only one thing (single responsibility) and do not operate on the different levels of the objects.

Nel tuo esempio, dato che hai un'istanza BClass come membro di MyAClass , il metodo DoSomething() dovrebbe solo accedere a proprietà / metodi di quel membro e mai proprietà di proprietà.

Risoluzione potenziale:

  1. Introduci metodo in MyBClass

    public class MyBClass
    {
        public MyCClass BClass { get; set; }
    
        public void DoSomething()
        {
            CClass.DoSomething();
        }
    }
    

    e chiamalo in MyAClass.DOSomething()

  2. Considera il refactoring del codice e sostituisci MyBClass membro con MyCClass .

risposta data 18.01.2012 - 17:52
fonte

Leggi altre domande sui tag