En mi proyecto C ++, tengo dos clases, Particle
y Contact
. En la Particle
clase, tengo una variable miembro std::vector<Contact> contacts
que contiene todos los contactos de un Particle
objeto, y las funciones miembro correspondientes getContacts()
y addContact(Contact cont)
. Por lo tanto, en "Particle.h", incluyo "Contact.h".
En la Contact
clase, me gustaría agregar código al constructor para Contact
que se llame Particle::addContact(Contact cont)
, de modo que contacts
se actualice para los dos Particle
objetos entre los que Contact
se agrega el objeto. Por lo tanto, tendría que incluir "Particle.h" en "Contact.cpp".
Mi pregunta es si esto es aceptable o buena práctica de codificación y, de lo contrario, cuál sería una mejor manera de implementar lo que estoy tratando de lograr (simplemente, actualizar automáticamente la lista de contactos para una partícula específica cada vez que un nuevo contacto es creado).
Estas clases estarán unidas por una Network
clase que tendrá N partículas ( std::vector<Particle> particles
) y Nc contactos ( std::vector<Contact> contacts
). Pero quería poder tener funciones como particles[0].getContacts()
: ¿está bien tener tales funciones en la Particle
clase en este caso, o hay una mejor "estructura" de asociación en C ++ para este propósito (de dos clases relacionadas que se utilizan en otra clase)? .
Es posible que necesite un cambio de perspectiva aquí en cómo me estoy acercando a esto. Dado que las dos clases están conectadas por un Network
objeto de clase, es típico que la organización de código / clase tenga información de conectividad totalmente controlada por el Network
objeto (en el sentido de que un objeto Particle no debe conocer sus contactos y, en consecuencia, no debe tener un getContacts()
miembro función). Entonces, para saber qué contactos tiene una partícula específica, necesitaría obtener esa información a través del Network
objeto (por ejemplo, usando network.getContacts(Particle particle)
).
¿Sería menos típico (quizás incluso desalentado) el diseño de clase C ++ para que un objeto Particle también tenga ese conocimiento (es decir, tenga múltiples formas de acceder a esa información, ya sea a través del objeto Network o el objeto Particle, lo que sea más conveniente) )?
fuente
Network
objeto de clase que contieneParticle
objetos yContact
objetos. Con ese conocimiento básico, puedo intentar evaluar si se ajusta o no a mis necesidades específicas, que todavía se están explorando / desarrollando a medida que avanzo en el proyecto.Respuestas:
Hay dos partes en su pregunta.
La primera parte es la organización de los archivos de encabezado C ++ y los archivos fuente. Esto se resuelve mediante el uso de la declaración directa y la separación de la declaración de clase (colocándolos en el archivo de encabezado) y el cuerpo del método (colocándolos en el archivo fuente). Además, en algunos casos se puede aplicar el lenguaje Pimpl ("puntero a la implementación") para resolver casos más difíciles. Utilice punteros de propiedad compartida (
shared_ptr
), punteros de propiedad única (unique_ptr
) y punteros no propietarios (puntero sin formato, es decir, el "asterisco") de acuerdo con las mejores prácticas.La segunda parte es cómo modelar objetos que están interrelacionados en forma de gráfico . Los gráficos generales que no son DAG (gráficos acíclicos dirigidos) no tienen una forma natural de expresar la propiedad de un árbol. En cambio, los nodos y las conexiones son todos metadatos que pertenecen a un único objeto gráfico. En este caso, no es posible modelar la relación nodo-conexión como agregaciones. Los nodos no "poseen" conexiones; las conexiones no "poseen" nodos. En cambio, son asociaciones, y tanto los nodos como las conexiones son "propiedad" del gráfico. El gráfico proporciona métodos de consulta y manipulación que operan en los nodos y las conexiones.
fuente
particles[0].getContacts()
: ¿sugiere en su último párrafo que no debería tener tales funciones en laParticle
clase, o que la estructura actual está bien porque están inherentemente relacionadas / asociadasNetwork
? ¿Existe una mejor "estructura" de asociación en C ++ en este caso?network.particle[p]
tendría un correspondientenetwork.contacts[p]
con los índices de sus contactos. De lo contrario, la Red y la Partícula de alguna manera ambos rastrean la misma información.Particle
objeto no debería ser consciente de sus contactos (por lo que no debería tener unagetContacts()
función miembro), y que esa información solo debería provenir del interior delNetwork
objeto? ¿Sería un mal diseño de clase C ++ para unParticle
objeto tener ese conocimiento (es decir, tener múltiples formas de acceder a esa información, a través delNetwork
objeto o delParticle
objeto, lo que parezca más conveniente)? Esto último parece tener más sentido para mí, pero tal vez necesito cambiar mi perspectiva sobre esto.Particle
saber algo acerca deContact
s oNetwork
s es que te vincula a una forma específica de representar esa relación. Las tres clases pueden tener que estar de acuerdo. Si, en cambio,Network
es el único que sabe o le importa, esa es solo una clase que debe cambiar si decide que otra representación es mejor.Particle
yContact
debe ser totalmente independiente, y la asociación entre ellos se define por elNetwork
objeto. Solo para estar completamente seguro, esto es (probablemente) lo que @rwong quiso decir cuando escribió: "tanto los nodos como las conexiones son" propiedad del "gráfico. El gráfico proporciona métodos de consulta y manipulación que operan en los nodos y las conexiones". , ¿derecho?Si lo entendí bien, el mismo objeto de contacto pertenece a más de un objeto de partículas, ya que representa algún tipo de contacto físico entre dos o más partículas, ¿verdad?
Entonces, lo primero que creo que es cuestionable es ¿por qué
Particle
tiene una variable miembrostd::vector<Contact>
? Debería ser astd::vector<Contact*>
o astd::vector<std::shared_ptr<Contact> >
en su lugar.addContact
entonces debería tener una firma diferente comoaddContact(Contact *cont)
o en suaddContact(std::shared_ptr<Contact> cont)
lugar.Esto hace innecesario incluir "Contact.h" en "Particle.h",
class Contact
será suficiente una declaración de "Particle.h" y una inclusión de "Contact.h" en "Particle.cpp".Luego la pregunta sobre el constructor. Quieres algo como
¿Derecho? Este diseño está bien, siempre que su programa siempre conozca las partículas relacionadas en el momento en que se debe crear un objeto de contacto.
Tenga en cuenta que si va por la
std::vector<Contact*>
ruta, debe invertir algunas ideas sobre la vida útil y la propiedad de losContact
objetos. Ninguna partícula "posee" sus contactos, un contacto probablemente tendrá que eliminarse solo siParticle
se destruyen ambos objetos relacionados . Usar en sustd::shared_ptr<Contact>
lugar resolverá este problema automáticamente. O deja que un objeto de "contexto circundante" tome posesión de partículas y contactos (como lo sugiere @rwong), y gestiona su vida útil.fuente
addContact(const std::shared_ptr<Contact> &cont)
másaddContact(std::shared_ptr<Contact> cont)
?move
paradigmaparticle1.getContacts()
yparticle2.getContacts()
entregar el mismoContact
objeto que representa el contacto físico entreparticle1
yparticle2
, y no dos objetos diferentes. Por supuesto, uno podría tratar de diseñar el sistema de una manera que no importe si hay dosContact
objetos disponibles al mismo tiempo que representen el mismo contacto físico. Esto implicaría hacerContact
inmutable, pero ¿estás seguro de que esto es lo que quieres?Sí, lo que describe es una forma muy aceptable de garantizar que cada
Contact
instancia esté en la lista de contactos de aParticle
.fuente
Lo que has hecho es correcto.
Otra forma ... Si el objetivo es asegurar que todos
Contact
estén en una lista, entonces usted podría:Contact
(constructores privados),Particle
clase,Particle
la clase de un amigoContact
,Particle
crear un método de fábrica que crea unContact
Entonces no tienes que incluir
particle.h
encontact
fuente
Network
clase, ¿eso cambia la estructura sugerida, o seguiría siendo la misma?Otra opción que puede considerar es hacer que el constructor de contactos que acepte una referencia de partícula tenga una plantilla. Esto permitirá que un contacto se agregue a cualquier contenedor que implemente
addContact(Contact)
.fuente