it-swarm.com.de

Wie löse ich die Interdependenz von Klassen in meinem C ++ - Code?

In meinem C++ - Projekt habe ich zwei Klassen, Particle und Contact. In der Klasse Particle habe ich eine Mitgliedsvariable std::vector<Contact> contacts, Die alle Kontakte eines Particle Objekts und die entsprechenden Mitgliedsfunktionen getContacts() und addContact(Contact cont). Daher füge ich in "Partikel.h" "Kontakt.h" ein.

In der Klasse Contact möchte ich dem Konstruktor Code für Contact hinzufügen, der Particle::addContact(Contact cont) aufruft, sodass contacts für beide aktualisiert wird Particle Objekte, zwischen denen das Contact Objekt hinzugefügt wird. Daher müsste ich "Particle.h" in "Contact.cpp" aufnehmen.

Meine Frage ist, ob dies eine akzeptable/gute Codierungspraxis ist oder nicht, und wenn nicht, was wäre ein besserer Weg, um das zu implementieren, was ich erreichen möchte (einfach ausgedrückt, die Liste der Kontakte für ein bestimmtes Partikel wird bei jedem neuen Kontakt automatisch aktualisiert geschaffen).


Diese Klassen werden durch eine Network Klasse miteinander verbunden, die N Partikel (std::vector<Particle> particles) Und Nc Kontakte (std::vector<Contact> contacts) Enthält. Aber ich wollte in der Lage sein, Funktionen wie particles[0].getContacts() zu haben - ist es in diesem Fall in Ordnung, solche Funktionen in der Klasse Particle zu haben, oder gibt es eine bessere Assoziations- "Struktur" in C++ für zu diesem Zweck (von zwei verwandten Klassen, die in einer anderen Klasse verwendet werden).


Möglicherweise brauche ich hier einen Perspektivwechsel, wenn ich mich dem nähere. Da die beiden Klassen durch ein Network - Klassenobjekt verbunden sind, ist es typisch für Code-/Klassenorganisation, dass Konnektivitätsinformationen vollständig vom Network - Objekt gesteuert werden (da ein Partikelobjekt dies nicht wissen sollte seine Kontakte und folglich sollte es keine getContacts() Member-Funktion haben). Um dann zu wissen, welche Kontakte ein bestimmtes Teilchen hat, müsste ich diese Informationen über das Objekt Network erhalten (z. B. unter Verwendung von network.getContacts(Particle particle)).

Wäre es weniger typisch (vielleicht sogar entmutigt), dass ein Partikelobjekt auch über dieses Wissen verfügt (dh über mehrere Möglichkeiten verfügt, auf diese Informationen zuzugreifen - entweder über das Netzwerkobjekt oder das Partikelobjekt, je nachdem, was zweckmäßiger erscheint? )?

10
AnInquiringMind

Ihre Frage besteht aus zwei Teilen.

Der erste Teil ist die Organisation von C++ - Header- und Quelldateien. Dies wird gelöst, indem die Vorwärtsdeklaration und die Trennung der Klassendeklaration (sie in die Header-Datei einfügen) und des Methodenkörpers (sie in die Quelldatei einfügen) verwendet werden ). Darüber hinaus kann man in einigen Fällen das Pimpl-Idiom ("Zeiger auf die Implementierung") anwenden, um schwierigere Fälle zu lösen. Verwenden Sie Zeiger mit gemeinsamem Besitz (shared_ptr), Einzelbesitzzeiger (unique_ptr) und nicht besitzende Zeiger (Rohzeiger, d. h. das "Sternchen") gemäß Best Practices.

Im zweiten Teil werden Objekte modelliert, die in Form eines Graphen miteinander verknüpft sind. Allgemeine Diagramme, die nicht DAGs (gerichtete azyklische Diagramme) sind, haben keine natürliche Art, baumähnliches Eigentum auszudrücken. Stattdessen sind die Knoten und Verbindungen alle Metadaten, die zu einem einzelnen Diagrammobjekt gehören. In diesem Fall ist es nicht möglich, die Knoten-Verbindungs-Beziehung als Aggregationen zu modellieren. Knoten "besitzen" keine Verbindungen; Verbindungen "besitzen" keine Knoten. Stattdessen handelt es sich um Zuordnungen, und sowohl Knoten als auch Verbindungen gehören dem Diagramm. Das Diagramm enthält Abfrage- und Manipulationsmethoden, die auf den Knoten und Verbindungen ausgeführt werden.

17
rwong

Wenn ich Sie richtig verstanden habe, gehört dasselbe Kontaktobjekt zu mehr als einem Partikelobjekt, da es eine Art physischen Kontakt zwischen zwei oder mehr Partikeln darstellt, richtig?

Das erste, was ich für fragwürdig halte, ist, warum Particle eine Mitgliedsvariable std::vector<Contact> Hat. Es sollte stattdessen ein std::vector<Contact*> Oder ein std::vector<std::shared_ptr<Contact> > Sein. addContact sollte dann stattdessen eine andere Signatur wie addContact(Contact *cont) oder addContact(std::shared_ptr<Contact> cont) haben.

Dies macht es unnötig, "Contact.h" in "Particle.h", eine Vorwärtsdeklaration von class Contact In "Particle.h" und ein Include von "Contact.h" in "Particle.cpp" aufzunehmen. wird genug sein.

Dann die Frage nach dem Konstruktor. Du willst so etwas wie

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

Recht? Dieses Design ist in Ordnung, solange Ihr Programm die zugehörigen Partikel zum Zeitpunkt der Erstellung eines Kontaktobjekts immer kennt.

Beachten Sie, dass Sie, wenn Sie den Weg std::vector<Contact*> Gehen, einige Gedanken über die Lebensdauer und den Besitz der Contact -Objekte investieren müssen. Kein Partikel "besitzt" seine Kontakte, ein Kontakt muss wahrscheinlich nur gelöscht werden, wenn beide verwandten Particle Objekte zerstört sind. Wenn Sie stattdessen std::shared_ptr<Contact> Verwenden, wird dieses Problem automatisch für Sie gelöst. Oder Sie lassen ein "umgebenden Kontext" -Objekt das Eigentum an Partikeln und Kontakten übernehmen (wie von @rwong vorgeschlagen) und deren Lebensdauer verwalten.

5
Doc Brown

Eine weitere Option, die Sie in Betracht ziehen könnten, besteht darin, den Kontaktkonstruktor so zu gestalten, dass er eine Vorlage für eine Partikelreferenz akzeptiert. Dadurch kann sich ein Kontakt jedem Container hinzufügen, der addContact(Contact) implementiert.

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
0
Erroneous

Ja, was Sie beschreiben, ist eine sehr akzeptable Methode, um sicherzustellen, dass jede Contact -Instanz in der Kontaktliste eines Particle enthalten ist.

Was Sie getan haben, ist richtig.

Ein anderer Weg ... Wenn das Ziel darin besteht, sicherzustellen, dass jedes Contact in einer Liste enthalten ist, können Sie:

  • blockerstellung von Contact (private Konstruktoren),
  • vorwärts deklarieren Particle Klasse,
  • mache Particle Klasse zu einem Freund von Contact,
  • erstellen Sie in Particle eine Factory-Methode, die ein Contact erstellt

Dann müssen Sie particle.h Nicht in contact einfügen

0