Come gestisci le dipendenze cross-class su distruzione / progettazione (più di una domanda C ++)

2

Quindi, se capisco correttamente, dai principi di progettazione SOLID, ogni classe dovrebbe mantenere una singola responsabilità.

Quindi dovrebbe esserci una classe che crea e gestisce una risorsa, una seconda classe che elabora l'elaborazione / aggiornamento delle risorse, la terza classe che collega la risorsa a qualche altra risorsa e così via.

La domanda è, come la esprimi in modo pulito nel codice?

Perché, in C ++, l'ordine di distruzione di classe gioca un ruolo importante.

Quindi ti trovi praticamente in una situazione in cui ottieni

ResourceClassInstanceManager cf;//does the ResourceClass creation, accounting and destruction?
ResourceClass &rcInstance = cf.GetResourceClass();//creates the instance of resource
ResourceClassUpdater rcu(&rcInstance);//updater that works on the class instance
ResourceConnector rcc(&rcInstance, &instanceOfAnotherClass);

Il problema qui è che se cf lascia prima l'oscilloscopio, o rcInstance viene cancellato prima di rcu o rcc. Le altre classi faranno comunque riferimento all'istanza non esistente di ResourceClass.

Questo sembra un po 'disordinato, poiché l'intera catena di creazione / distruzione deve essere seguita con molta attenzione e diventa davvero preoccupante non appena si hanno puntatori a oggetti e livelli di nidificazione più profondi.

Esiste una soluzione pulita per questo problema di progettazione?

    
posta Coder 19.08.2011 - 15:09
fonte

3 risposte

1

Una soluzione potrebbe essere l'uso di puntatori intelligenti (ad esempio, in Qt si ha la classe QPointer) invece dei riferimenti: non appena un oggetto viene distrutto, un puntatore intelligente che lo stava puntando verrà impostato su NULL. Quindi non tenterai mai di fare riferimento a un oggetto che è già stato distrutto.

Naturalmente, è necessario anche un meccanismo per evitare un'eccezione del puntatore NULL, ad es. dovresti controllare il valore di un puntatore intelligente prima di dereferenziarlo.

Informazioni aggiuntive

Secondo me, un principio importante è che deve essere chiaro quali parti del codice (ad es. quali oggetti) gestiscono un certo oggetto e quali parti del codice usano solo (o osserva ) l'oggetto.

La mia strategia abituale è la seguente.

Tutti i gestori avvolgono il puntatore su un oggetto dato in un boost :: shared_pointer (vedi anche suggerimento di DeadMG). Questo è un puntatore intelligente con il conteggio dei riferimenti: quando l'ultimo puntatore intelligente per un oggetto viene distrutto, anche l'oggetto di riferimento (gestito) viene distrutto.

Inoltre, ho observers , che memorizzano semplicemente un puntatore all'oggetto ma non hanno alcuna influenza sulla sua durata. Ho usato QPointer per questo: quando l'oggetto viene distrutto, QPointer viene automaticamente impostato su NULL. In questo modo, l'osservatore può rilevare che l'oggetto non esiste più.

Un punto importante qui è che gli oggetti stessi possono osservare o gestire altri oggetti. I puntatori tra oggetti portano a grafici di oggetti, ma normalmente puoi decomporre questo grafico in una struttura ad albero (aggregazione) con riferimenti incrociati (associazioni ordinarie) tra fratelli dell'albero.

In questo caso, utilizzo i puntatori condivisi per implementare la relazione padre / figlio (ogni nodo gestisce i suoi figli, e i bambini vengono automaticamente distrutti quando il padre viene distrutto) e QPointer per i riferimenti tra bambini o per la relazione figlio / genitore.

    
risposta data 19.08.2011 - 16:00
fonte
2

No. La distruzione avviene in ordine inverso di costruzione. rcc verrà prima distrutto. Il problema fondamentale qui è che stai gettando in giro cattivi riferimenti - cioè, stai gettando un riferimento a un ResourceClass senza alcun controllo su di esso. Dovrebbe essere racchiuso in un puntatore intelligente appropriato.

Inoltre, trovo il tuo design di classe molto altamente discutibile. Qual è in realtà il lavoro di una di queste classi eccetto la stessa ResourceClass? Non ho bisogno di una classe helper per chiamare new , o chiamare una funzione update o connect su un ResourceClass.

    
risposta data 19.08.2011 - 16:53
fonte
1

Penso che tu stia usando una definizione di "responsabilità" troppo stretta. La tua definizione di "responsabilità" sembra essere più simile a "attività" o "azione".

Mi aspetto che la responsabilità di una classe di risorse possa essere espressa in una frase simile a "Gestisci la durata e l'accesso a X, consentendo aggiornamenti e connessioni a X".

Una classe è un'entità più grande di, diciamo, una funzione, quindi dovresti aspettarti che la sua responsabilità sia più ampia. Una funzione membro normalmente avrebbe una responsabilità più ristretta, ma potrebbe anche avere una singola responsabilità. Ad esempio, la funzione membro Update potrebbe avere la responsabilità di "convalidare ed eseguire l'aggiornamento richiesto su X".

    
risposta data 20.08.2011 - 11:09
fonte

Leggi altre domande sui tag