Come risolvere l'interdipendenza di classe nel mio codice C ++?


10

Nel mio progetto C ++, ho due classi Particlee Contact. Nella Particleclasse, ho una variabile membro std::vector<Contact> contactsche contiene tutti i contatti di un Particleoggetto e le corrispondenti funzioni membro getContacts()e addContact(Contact cont). Pertanto, in "Particle.h" includo "Contact.h".

Nella Contactclasse, vorrei aggiungere il codice al costruttore per Contactquello che chiamerà Particle::addContact(Contact cont), in modo che contactsvenga aggiornato per entrambi gli Particleoggetti tra i quali Contactviene aggiunto l' oggetto. Quindi, dovrei includere "Particle.h" in "Contact.cpp".

La mia domanda è se questo sia accettabile o meno una buona pratica di codifica e, in caso contrario, quale sarebbe un modo migliore per implementare ciò che sto cercando di ottenere (in poche parole, aggiornando automaticamente l'elenco dei contatti per una particella specifica ogni volta che un nuovo contatto è creato).


Queste classi saranno legate insieme da una Networkclasse che avrà N particelle ( std::vector<Particle> particles) e contatti Nc ( std::vector<Contact> contacts). Ma volevo essere in grado di avere funzioni come particles[0].getContacts()- va bene avere tali funzioni nella Particleclasse in questo caso, oppure esiste una "struttura" di associazione migliore in C ++ per questo scopo (di due classi correlate usate in un'altra classe) .


Potrei aver bisogno di un cambiamento di prospettiva qui nel modo in cui mi sto avvicinando a questo. Dato che le due classi sono connesse da un Networkoggetto class, è tipica dell'organizzazione codice / classe avere informazioni di connettività interamente controllate Networkdall'oggetto (in quanto un oggetto Particle non dovrebbe essere a conoscenza dei suoi contatti e, di conseguenza, non dovrebbe avere un getContacts()membro funzione). Quindi, per sapere quali contatti ha una specifica particella, avrei bisogno di ottenere tali informazioni attraverso l' Networkoggetto (ad esempio, usando network.getContacts(Particle particle)).

Sarebbe meno tipico (forse anche scoraggiato) il design della classe C ++ per un oggetto Particle anche avere quella conoscenza (cioè avere più modi per accedere a tali informazioni - attraverso l'oggetto Network o l'oggetto Particle, a seconda di quale sembra più conveniente )?


4
Ecco un discorso da cppcon 2017 - I tre strati di intestazioni: youtu.be/su9ittf-ozk
Robert Andrzejuk

3
Domande che contengono parole come "migliore", "migliore" e "accettabile" sono senza risposta a meno che non sia possibile indicare i propri criteri di valutazione specifici.
Robert Harvey,

Grazie per la modifica, sebbene cambiare la tua formulazione in "tipica" sia solo una questione di popolarità. Ci sono ragioni per cui la codifica viene fatta in un modo o nell'altro, e mentre la popolarità può essere un'indicazione che una tecnica è "buona" (per alcune definizioni di "buono"), può anche essere un'indicazione del culto del carico.
Robert Harvey,

@RobertHarvey Ho rimosso "meglio" e "male" nella mia sezione finale. Suppongo che sto chiedendo l'approccio tipico (forse anche favorito / incoraggiato) quando hai un Networkoggetto di classe che contiene Particleoggetti e Contactoggetti. Con questa conoscenza di base, posso quindi provare a valutare se si adatta o meno alle mie esigenze specifiche, che sono ancora esplorate / sviluppate mentre procedo nel progetto.
AnInquiringMind

@RobertHarvey Suppongo di essere abbastanza nuovo per scrivere progetti C ++ completamente da zero che sto bene con l'apprendimento di ciò che è "tipico" e "popolare". Spero che a un certo punto avrò abbastanza informazioni per essere in grado di capire perché un'altra implementazione è effettivamente migliore, ma per ora, voglio solo assicurarmi di non avvicinarmi a questo in modo completamente osseo!
AnInquiringMind

Risposte:


17

Ci sono due parti nella tua domanda.

La prima parte è l'organizzazione dei file di intestazione C ++ e dei file di origine. Ciò viene risolto utilizzando la dichiarazione diretta e la separazione della dichiarazione di classe (inserendole nel file di intestazione) e del corpo del metodo (inserendole nel file di origine). Inoltre, in alcuni casi si può applicare il linguaggio Pimpl ("puntatore all'implementazione") per risolvere casi più difficili. Utilizzare i puntatori di proprietà condivisa ( shared_ptr), i puntatori di proprietà singola ( unique_ptr) e i puntatori non proprietari (puntatore non elaborato, ovvero l'asterisco) in base alle best practice.

La seconda parte è come modellare oggetti collegati tra loro sotto forma di un grafico . I grafici generali che non sono DAG (grafici aciclici diretti) non hanno un modo naturale di esprimere la proprietà simile ad un albero. Al contrario, i nodi e le connessioni sono tutti metadati che appartengono a un singolo oggetto grafico. In questo caso, non è possibile modellare la relazione nodo-connessione come aggregazioni. I nodi non "possiedono" connessioni; le connessioni non "possiedono" nodi. Al contrario, sono associazioni e entrambi i nodi e le connessioni sono "di proprietà" del grafico. Il grafico fornisce metodi di query e manipolazione che operano su nodi e connessioni.


Grazie per la risposta! In realtà ho una classe Network che avrà N particelle e contatti Nc. Ma volevo essere in grado di avere funzioni come particles[0].getContacts()- stai suggerendo nel tuo ultimo paragrafo che non dovrei avere tali funzioni nella Particleclasse, o che la struttura attuale va bene perché sono intrinsecamente correlate / associate Network? In questo caso esiste una "struttura" di associazione migliore in C ++?
AnInquiringMind

1
In genere, la rete è responsabile della conoscenza delle relazioni tra oggetti. Se si utilizza un elenco di adiacenza, ad esempio, la particella network.particle[p]avrebbe una corrispondenza network.contacts[p]con gli indici dei suoi contatti. Altrimenti, sia la rete che la particella stanno in qualche modo monitorando le stesse informazioni.
Inutile

@Useless Sì, è lì che non sono sicuro di come procedere. Quindi stai dicendo che l' Particleoggetto non dovrebbe essere a conoscenza dei suoi contatti (quindi non dovrei avere una getContacts()funzione membro) e che tali informazioni dovrebbero provenire solo dall'interno Networkdell'oggetto? Sarebbe un cattivo progetto di classe C ++ per un Particleoggetto avere quella conoscenza (cioè avere più modi per accedere a quell'informazione - attraverso l' Networkoggetto o l' Particleoggetto, a seconda di quale sembra più conveniente)? Quest'ultimo sembra avere più senso per me, ma forse ho bisogno di cambiare la mia prospettiva su questo.
AnInquiringMind

1
@PhysicsCodingEnthusiast: il problema di Particlesapere qualcosa su Contacts o Networks è che ti lega a un modo specifico di rappresentare quella relazione. Tutte e tre le classi potrebbero dover essere d'accordo. Se invece Networkè l'unico che conosce o si preoccupa, è solo una classe che deve cambiare se decidi che un'altra rappresentazione è migliore.
cHao,

@cHao Ok, questo ha senso. Quindi Particlee Contactdovrebbe essere completamente separato e l'associazione tra loro è definita Networkdall'oggetto. Giusto per essere completamente sicuri, questo è (probabilmente) ciò che @rwong intendeva quando scrisse, "sia i nodi che le connessioni sono" di proprietà "del grafico. Il grafico fornisce metodi di query e manipolazione che operano sui nodi e sulle connessioni". , giusto?
AnInquiringMind

5

Se ho capito bene, lo stesso oggetto di contatto appartiene a più di un oggetto particella, dal momento che rappresenta un tipo di contatto fisico tra due o più particelle, giusto?

Quindi la prima cosa che ritengo discutibile è perché Particleha una variabile membro std::vector<Contact>? Dovrebbe essere un std::vector<Contact*>o un std::vector<std::shared_ptr<Contact> >invece. addContactquindi dovrebbe avere una firma diversa come addContact(Contact *cont)o addContact(std::shared_ptr<Contact> cont)invece.

Ciò rende superfluo includere "Contact.h" in "Particle.h", class Contactsarà sufficiente una dichiarazione in avanti di "Particle.h" e una inclusione di "Contact.h" in "Particle.cpp".

Quindi la domanda sul costruttore. Vuoi qualcosa di simile

 Contact(Particle &p1, Particle &p2)
 {
      p1.addContact(this);
      p2.addContact(this);
 }

Giusto? Questo design è ok, purché il tuo programma conosca sempre le particelle correlate nel momento in cui un oggetto di contatto deve essere creato.

Nota, se segui il std::vector<Contact*>percorso, devi investire alcuni pensieri sulla durata e sulla proprietà degli Contactoggetti. Nessuna particella "possiede" i suoi contatti, probabilmente un contatto dovrà essere eliminato solo se entrambi gli Particleoggetti correlati vengono distrutti. L'utilizzo std::shared_ptr<Contact>invece risolverà questo problema automaticamente. Oppure lasci che un oggetto "contesto circostante" assuma la proprietà di particelle e contatti (come suggerito da @rwong) e ne gestisca la vita.


Non vedo il vantaggio di addContact(const std::shared_ptr<Contact> &cont)over addContact(std::shared_ptr<Contact> cont)?
Caleth,

@Caleth: questo è stato discusso qui: stackoverflow.com/questions/3310737/… - "const" non è davvero importante qui, ma passare oggetti per riferimento (e scalari per valore) è il linguaggio standard in C ++.
Doc Brown,

2
Molte di queste risposte sembrano provenire da un pre- moveparadigma
Caleth,

@Caleth: ok, per rendere felici tutti i nitpicker, ho cambiato questa parte abbastanza poco importante della mia risposta.
Doc Brown,

1
@PhysicsCodingEnthusiast: no, si tratta principalmente di creare particle1.getContacts()e particle2.getContacts()consegnare lo stesso Contactoggetto che rappresenta il contatto fisico tra particle1e particle2, e non due oggetti diversi. Naturalmente, si potrebbe provare a progettare il sistema in un modo che non importa se ci sono due Contactoggetti disponibili contemporaneamente che rappresentano lo stesso contatto fisico. Ciò implicherebbe di renderlo Contactimmutabile, ma sei sicuro che questo è quello che vuoi?
Doc Brown,

0

Sì, quello che descrivi è un modo molto accettabile per garantire che ogni Contactistanza sia nell'elenco dei contatti di a Particle.


Grazie per la risposta. Ho letto alcuni suggerimenti secondo cui è necessario evitare una coppia di classi interdipendenti (ad esempio, in "C ++ Design Patterns and Derivatives Pricing" di MS Joshi), ma a quanto pare non è necessariamente corretto? Per curiosità, c'è forse un altro modo di implementare questo aggiornamento automatico senza bisogno di interdipendenza?
AnInquiringMind

4
@PhysicsCodingEnthusiast: Avere classi interdipendenti crea ogni tipo di difficoltà e dovresti cercare di evitarle. Ma a volte, due classi sono così strettamente correlate tra loro che rimuovere l'interdipendenza tra loro causa più problemi dell'interdipendenza stessa.
Bart van Ingen Schenau,

0

Quello che hai fatto è corretto.

Un altro modo ... Se l'obiettivo è quello di garantire che tutti Contactsiano in un elenco, è possibile:

  • blocco della creazione di Contact(costruttori privati),
  • dichiarare in avanti la Particleclasse,
  • fare della Particleclasse un amico di Contact,
  • in Particlecrea un metodo factory che crea aContact

Quindi non c'è bisogno di includere particle.hincontact


Grazie per la risposta! Sembra un modo utile per implementarlo. Mi chiedo solo, con la mia modifica alla domanda iniziale relativa alla Networkclasse, che cambia la struttura suggerita o sarebbe sempre la stessa?
AnInquiringMind

Dopo aver aggiornato la tua domanda, sta cambiando ambito. ... Ora ti stai chiedendo dell'architettura della tua applicazione, quando in precedenza si trattava di un problema tecnico.
Robert Andrzejuk,

0

Un'altra opzione che potresti prendere in considerazione è rendere il costruttore del contatto che accetta un riferimento a una particella come modello. Ciò consentirà a un contatto di aggiungersi a qualsiasi contenitore implementato addContact(Contact).

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.