No meu projeto C ++, tenho duas classes Particle
e Contact
. Na Particle
classe, eu tenho uma variável de membro std::vector<Contact> contacts
que contém todos os contatos de um Particle
objeto e as funções de membro correspondentes getContacts()
e addContact(Contact cont)
. Assim, em "Particle.h", incluo "Contact.h".
Na Contact
classe, eu gostaria de adicionar código ao construtor para o Contact
que será chamado Particle::addContact(Contact cont)
, para que contacts
seja atualizado para os dois Particle
objetos entre os quais o Contact
objeto está sendo adicionado. Assim, eu teria que incluir "Particle.h" em "Contact.cpp".
Minha pergunta é se isso é aceitável / uma boa prática de codificação e, se não, qual seria a melhor maneira de implementar o que estou tentando alcançar (basta colocar, atualizar automaticamente a lista de contatos de uma partícula específica sempre que um novo contato é criado).
Essas classes serão ligadas por uma Network
classe que terá N partículas ( std::vector<Particle> particles
) e contatos Nc ( std::vector<Contact> contacts
). Mas eu queria poder ter funções como particles[0].getContacts()
- é aceitável ter essas funções na Particle
classe nesse caso, ou existe uma "estrutura" de associação melhor em C ++ para esse propósito (duas classes relacionadas sendo usadas em outra classe) .
Talvez eu precise de uma mudança de perspectiva aqui na maneira como estou abordando isso. Como as duas classes são conectadas por um Network
objeto de classe, é típico da organização de código / classe ter informações de conectividade totalmente controladas pelo Network
objeto (em que um objeto Particle não deve estar ciente de seus contatos e, consequentemente, não deve ter um getContacts()
membro função). Então, para saber quais contatos uma partícula específica possui, eu precisaria obter essas informações através do Network
objeto (por exemplo, usando network.getContacts(Particle particle)
).
Seria menos típico (talvez até desanimado) o design da classe C ++ para um objeto Particle ter esse conhecimento também (por exemplo, ter várias maneiras de acessar essas informações - por meio do objeto Rede ou do objeto Particle, o que parecer mais conveniente )?
fonte
Network
objeto de classe que contémParticle
objetos eContact
objetos. Com esse conhecimento básico, posso tentar avaliar se ele se encaixa ou não nas minhas necessidades específicas, que ainda estão sendo exploradas / desenvolvidas à medida que avançamos no projeto.Respostas:
Há duas partes na sua pergunta.
A primeira parte é a organização dos arquivos de cabeçalho e arquivos de origem C ++. Isso é resolvido usando a declaração direta e a separação da declaração da classe (colocando-as no arquivo de cabeçalho) e o corpo do método (colocando-as no arquivo de origem). Além disso, em alguns casos, pode-se aplicar o idioma Pimpl ("ponteiro para implementação") para resolver casos mais difíceis. Use ponteiros de propriedade compartilhada (
shared_ptr
), ponteiros de propriedade única (unique_ptr
) e ponteiros não proprietários (ponteiro bruto, ou seja, o "asterisco") de acordo com as práticas recomendadas.A segunda parte é como modelar objetos inter-relacionados na forma de um gráfico . Gráficos gerais que não são DAGs (gráficos acíclicos direcionados) não têm uma maneira natural de expressar a propriedade de uma árvore. Em vez disso, os nós e as conexões são todos os metadados que pertencem a um único objeto gráfico. Nesse caso, não é possível modelar o relacionamento de conexão do nó como agregações. Nós não "possuem" conexões; conexões não "possuem" nós. Em vez disso, são associações e os nós e as conexões são "de propriedade" do gráfico. O gráfico fornece métodos de consulta e manipulação que operam nos nós e nas conexões.
fonte
particles[0].getContacts()
- você está sugerindo no seu último parágrafo que eu não deveria ter essas funções naParticle
classe ou que a estrutura atual está correta porque elas são inerentemente relacionadas / associadasNetwork
? Existe uma melhor "estrutura" de associação em C ++ neste caso?network.particle[p]
teria uma correspondêncianetwork.contacts[p]
com os índices de seus contatos. Caso contrário, a rede e a partícula estão, de alguma forma, rastreando a mesma informação.Particle
objeto não deve estar ciente de seus contatos (então eu não deveria ter umagetContacts()
função de membro) e que essas informações devem vir apenas de dentro doNetwork
objeto? Seria um projeto ruim da classe C ++ para umParticle
objeto ter esse conhecimento (por exemplo, ter várias maneiras de acessar essas informações - por meio doNetwork
objeto ou doParticle
objeto, o que parecer mais conveniente)? O último parece fazer mais sentido para mim, mas talvez eu precise mudar minha perspectiva sobre isso.Particle
saber algo sobreContact
s ouNetwork
s é que ele o vincula a uma maneira específica de representar esse relacionamento. Todas as três classes podem ter que concordar. Se, em vez disso,Network
é o único que sabe ou se importa, é apenas uma classe que precisa mudar se você decidir que outra representação é melhor.Particle
eContact
deve ser completamente separado, e a associação entre eles é definida peloNetwork
objeto. Apenas para ter certeza, é isso (provavelmente) o que @rwong quis dizer quando ele escreveu: "nós e conexões são" de propriedade "do gráfico. O gráfico fornece métodos de consulta e manipulação que operam nos nós e nas conexões". , direita?Se eu entendi direito, o mesmo objeto de contato pertence a mais de um objeto de partícula, pois representa algum tipo de contato físico entre duas ou mais partículas, certo?
Então a primeira coisa que acho questionável é por
Particle
que uma variável membrostd::vector<Contact>
? Deve ser umstd::vector<Contact*>
ou um emstd::vector<std::shared_ptr<Contact> >
vez disso.addContact
então deve ter assinatura diferente comoaddContact(Contact *cont)
ouaddContact(std::shared_ptr<Contact> cont)
não.Isso torna desnecessário incluir "Contact.h" em "Particle.h", uma declaração direta de
class Contact
"Particle.h" e uma inclusão de "Contact.h" em "Particle.cpp" será suficiente.Então a pergunta sobre o construtor. Você quer algo como
Direita? Esse design é bom, desde que o seu programa sempre saiba as partículas relacionadas no momento em que um objeto de contato deve ser criado.
Observe que, se você seguir o
std::vector<Contact*>
caminho, precisará investir alguns pensamentos sobre a vida útil e a propriedade dosContact
objetos. Nenhuma partícula "possui" seus contatos, um contato provavelmente terá que ser excluído apenas se os doisParticle
objetos relacionados forem destruídos. Usar emstd::shared_ptr<Contact>
vez disso resolverá esse problema automaticamente para você. Ou você deixa um objeto de "contexto circundante" assumir a propriedade de partículas e contatos (como sugerido por @rwong) e gerenciar sua vida útil.fonte
addContact(const std::shared_ptr<Contact> &cont)
acabaraddContact(std::shared_ptr<Contact> cont)
?move
paradigmaparticle1.getContacts()
eparticle2.getContacts()
entregar o mesmoContact
objeto que representa o contato físico entreparticle1
eparticle2
, e não dois objetos diferentes. Obviamente, pode-se tentar projetar o sistema de uma maneira que não importe se houver doisContact
objetos disponíveis ao mesmo tempo, representando o mesmo contato físico. Isso envolveria tornarContact
imutável, mas você tem certeza de que é isso que deseja?Sim, o que você descreve é uma maneira muito aceitável de garantir que cada
Contact
instância esteja na lista de contatos de aParticle
.fonte
O que você fez está correto.
Outra maneira ... Se o objetivo é garantir que todos
Contact
estejam em uma lista, você pode:Contact
(construtores privados),Particle
classe a frente ,Particle
classe um amigoContact
,Particle
criar um método de fábrica que cria umContact
Então você não tem que incluir
particle.h
nacontact
fonte
Network
classe, isso altera a estrutura sugerida ou ainda seria a mesma?Outra opção que você pode considerar é criar o construtor Contact que aceita um modelo de referência de Partícula. Isso permitirá que um contato se adicione a qualquer contêiner implementado
addContact(Contact)
.fonte