Como resolver a interdependência de classe no meu código C ++?

10

No meu projeto C ++, tenho duas classes Particlee Contact. Na Particleclasse, eu tenho uma variável de membro std::vector<Contact> contactsque contém todos os contatos de um Particleobjeto e as funções de membro correspondentes getContacts()e addContact(Contact cont). Assim, em "Particle.h", incluo "Contact.h".

Na Contactclasse, eu gostaria de adicionar código ao construtor para o Contactque será chamado Particle::addContact(Contact cont), para que contactsseja atualizado para os dois Particleobjetos entre os quais o Contactobjeto 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 Networkclasse 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 Particleclasse 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 Networkobjeto de classe, é típico da organização de código / classe ter informações de conectividade totalmente controladas pelo Networkobjeto (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 Networkobjeto (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 )?

AnInquiringMind
fonte
4
Aqui está uma conversa de cppcon 2017 - As Camadas Três dos cabeçalhos: youtu.be/su9ittf-ozk
Robert Andrzejuk
3
Perguntas que contenham palavras como "melhor", "melhor" e "aceitável" não podem ser respondidas, a menos que você possa declarar seus critérios específicos de avaliação.
Robert Harvey
Obrigado pela edição, embora alterar sua redação para "típico" apenas a torne uma questão de popularidade. Há razões pelas quais a codificação é feita de uma maneira ou de outra e, embora a popularidade possa ser uma indicação de que uma técnica é "boa" (para alguma definição de "boa"), também pode ser uma indicação de cultivo de carga.
9788 Robert
@RobertHarvey Eu removi "melhor" e "ruim" na minha seção final. Suponho que estou pedindo a abordagem típica (talvez até favorecida / encorajada) quando você tem um Networkobjeto de classe que contém Particleobjetos e Contactobjetos. 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.
AnInquiringMind
@RobertHarvey Suponho que sou novo o suficiente para escrever projetos C ++ completamente do zero e que estou bem em aprender o que é "típico" e "popular". Espero ter uma visão suficiente em algum momento para poder entender por que outra implementação é realmente melhor, mas, por enquanto, só quero ter certeza de que não estou abordando isso de uma maneira completamente irracional!
precisa saber é o seguinte

Respostas:

17

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.

rwong
fonte
Obrigado pela resposta! Na verdade, eu tenho uma classe de rede que terá N partículas e contatos Nc. Mas eu queria poder ter funções como particles[0].getContacts()- você está sugerindo no seu último parágrafo que eu não deveria ter essas funções na Particleclasse ou que a estrutura atual está correta porque elas são inerentemente relacionadas / associadas Network? Existe uma melhor "estrutura" de associação em C ++ neste caso?
AnInquiringMind 8/07/17
1
Geralmente, a rede é responsável por conhecer os relacionamentos entre os objetos. Se você usar uma lista de adjacências, por exemplo, a partícula network.particle[p]teria uma correspondência network.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.
Inútil
@ Useless Sim, é aí que não tenho certeza de como proceder. Então você está dizendo que o Particleobjeto não deve estar ciente de seus contatos (então eu não deveria ter uma getContacts()função de membro) e que essas informações devem vir apenas de dentro do Networkobjeto? Seria um projeto ruim da classe C ++ para um Particleobjeto ter esse conhecimento (por exemplo, ter várias maneiras de acessar essas informações - por meio do Networkobjeto ou do Particleobjeto, o que parecer mais conveniente)? O último parece fazer mais sentido para mim, mas talvez eu precise mudar minha perspectiva sobre isso.
AnInquiringMind 8/17
1
@PhysicsCodingEnthusiast: O problema de Particlesaber algo sobre Contacts ou Networks é 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.
Chao
@cHao Ok, isso faz sentido. Portanto, Particlee Contactdeve ser completamente separado, e a associação entre eles é definida pelo Networkobjeto. 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?
AnInquiringMind
5

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 Particleque uma variável membro std::vector<Contact>? Deve ser um std::vector<Contact*>ou um em std::vector<std::shared_ptr<Contact> >vez disso. addContactentão deve ter assinatura diferente como addContact(Contact *cont)ou addContact(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

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

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 dos Contactobjetos. Nenhuma partícula "possui" seus contatos, um contato provavelmente terá que ser excluído apenas se os dois Particleobjetos relacionados forem destruídos. Usar em std::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.

Doc Brown
fonte
Não vejo o benefício de addContact(const std::shared_ptr<Contact> &cont)acabar addContact(std::shared_ptr<Contact> cont)?
#
@ Caleth: isso foi discutido aqui: stackoverflow.com/questions/3310737/… - "const" não é realmente importante aqui, mas passar objetos por referência (e escalares por valor) é o idioma padrão em C ++.
Doc Brown
2
Muitas dessas respostas parecem ser a partir de uma pré- moveparadigma
Caleth
@ Caleth: ok, para manter todos os nitpickers felizes, mudei essa parte sem importância da minha resposta.
Doc Brown
1
@PhysicsCodingEnthusiast: não, trata-se principalmente de criar particle1.getContacts()e particle2.getContacts()entregar o mesmo Contactobjeto que representa o contato físico entre particle1e particle2, e não dois objetos diferentes. Obviamente, pode-se tentar projetar o sistema de uma maneira que não importe se houver dois Contactobjetos disponíveis ao mesmo tempo, representando o mesmo contato físico. Isso envolveria tornar Contactimutável, mas você tem certeza de que é isso que deseja?
Doc Brown
0

Sim, o que você descreve é ​​uma maneira muito aceitável de garantir que cada Contactinstância esteja na lista de contatos de a Particle.

Bart van Ingen Schenau
fonte
Obrigado pela resposta. Eu tinha lido algumas sugestões de que ter um par de classes interdependentes deve ser evitado (por exemplo, em "C ++ Design Patterns and Derivatives Pricing" por MS Joshi), mas aparentemente isso não é necessariamente correto? Por curiosidade, existe talvez outra maneira de implementar essa atualização automática sem precisar de interdependência?
AnInquiringMind 8/17/17
4
@PhysicsCodingEnthusiast: Ter classes interdependentes cria todos os tipos de dificuldades e você deve evitá-las. Mas, às vezes, duas classes estão tão intimamente relacionadas entre si que remover a interdependência entre elas causa mais problemas do que a própria interdependência.
Bart van Ingen Schenau 8/11
0

O que você fez está correto.

Outra maneira ... Se o objetivo é garantir que todos Contactestejam em uma lista, você pode:

  • bloquear a criação de Contact(construtores privados),
  • declarar Particleclasse a frente ,
  • faça da Particleclasse um amigo Contact,
  • em Particlecriar um método de fábrica que cria umContact

Então você não tem que incluir particle.hnacontact

Robert Andrzejuk
fonte
Obrigado pela resposta! Essa parece ser uma maneira útil de implementar isso. Apenas imaginando, com minha edição da pergunta inicial sobre a Networkclasse, isso altera a estrutura sugerida ou ainda seria a mesma?
AnInquiringMind
Depois de atualizar sua pergunta, ela está mudando o escopo. ... Agora você está perguntando sobre a arquitetura do seu aplicativo, quando anteriormente era sobre um problema técnico.
Robert Andrzejuk
0

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).

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
Errôneo
fonte