É um projeto ruim ter duas classes que precisam uma da outra?
Estou escrevendo um pequeno jogo no qual tenho uma GameEngine
classe que tem alguns GameState
objetos. Para acessar vários métodos de renderização, esses GameState
objetos também precisam conhecer a GameEngine
classe - portanto, é uma dependência circular.
Você chamaria isso de design ruim? Estou apenas perguntando, porque não tenho muita certeza e, neste momento, ainda sou capaz de refatorar essas coisas.
c++
architecture
shad0w
fonte
fonte
Respostas:
Não é um projeto ruim por natureza, mas pode facilmente sair do controle. Na verdade, você está usando esse design em seus códigos diários. Por exemplo, o vetor sabe que é o primeiro iterador e os iteradores têm ponteiro para o contêiner.
Agora, no seu caso, é muito melhor ter duas classes separadas para GameEnigne e GameState. Como basicamente esses dois estão fazendo coisas diferentes, e mais tarde você pode definir muitas classes que herdam o GameState (como um GameState para cada cena do jogo). E ninguém pode negar a necessidade de ter acesso um ao outro. Basicamente, o GameEngine está executando gamestates, portanto, ele deve ter um ponteiro para eles. E o GameState está usando os recursos definidos no GameEngine (como renderizados, gerente de física etc.).
Você não pode e não deve combinar essas duas classes uma com a outra, já que elas estão fazendo coisas diferentes por natureza e a combinação resultará em uma classe muito grande da qual ninguém gosta.
Até agora sabemos que precisamos de dependência circular no design. Existem várias maneiras de criar isso com segurança:
Para concluir sua resposta, você pode usar qualquer um desses três métodos, mas precisa tomar cuidado para não usar nenhum deles, pois, como eu disse, todos esses designs são realmente perigosos e podem facilmente resultar em um código impossível de manter.
fonte
É um pouco de cheiro de código , mas pode-se sair com ele. Se essa é a maneira mais fácil e rápida de colocar seu jogo em funcionamento, siga em frente. Mas lembre-se disso, porque há uma boa chance de você precisar refatorá-lo em algum momento.
O problema do C ++ é que as dependências circulares não serão compiladas com tanta facilidade ; portanto, é uma idéia melhor se livrar delas, em vez de gastar tempo corrigindo sua compilação.
Vejo esta pergunta no SO para mais algumas opiniões.
Não, ainda é melhor do que colocar tudo em uma classe.
Não é tão bom, mas na verdade é bem próximo da maioria das implementações que já vi. Geralmente, você tem uma classe de gerente para os estados dos jogos ( cuidado! ) E uma classe de renderizador, e é bastante comum que eles sejam singletons. Portanto, a dependência circular é "oculta", mas é potencialmente lá.
Além disso, como você foi informado nos comentários, é um pouco estranho que as classes de estado do jogo executem algum tipo de renderização. Eles devem conter apenas informações de estado e a renderização deve ser manipulada por um renderizador ou por algum componente gráfico dos próprios objetos do jogo.
Agora pode haver o design final . Estou curioso para ver se outras respostas trazem uma boa idéia. Ainda assim, você provavelmente é o único que pode encontrar o melhor design para o seu jogo.
fonte
Muitas vezes, é considerado um design ruim ter que ter duas classes que se referem diretamente uma à outra, sim. Em termos práticos, pode ser mais difícil seguir o fluxo de controle através do código, a propriedade dos objetos e sua vida útil podem ser complicadas. Isso significa que nenhuma classe é reutilizável sem a outra. Isso pode significar que o fluxo de controle deve realmente viver fora das duas dessas classes em uma terceira classe 'mediadora', e assim por diante.
No entanto, é muito comum a 2 objetos se referirem um ao outro, e a diferença aqui é que geralmente o relacionamento em uma direção é mais abstrato. Por exemplo, em um sistema Model-View-Controller, o objeto View pode conter uma referência ao objeto Model e saberá tudo sobre ele, podendo acessar todos os seus métodos e propriedades para que a View possa se preencher com dados relevantes . O objeto Model pode reter uma referência para o View para que ele possa fazer a atualização quando seus próprios dados forem alterados. Mas, em vez de o Modelo ter uma referência de Visualização - o que tornaria o Modelo dependente da Visualização - geralmente a Visualização implementa uma interface Observável, geralmente com apenas 1
Update()
e o Modelo mantém uma referência a um objeto Observável, que pode ser uma Visualização. Quando o Modelo muda, ele chamaUpdate()
todos os seus Observáveis, e a Visualização é implementadaUpdate()
retornando ao Modelo e recuperando qualquer informação atualizada. O benefício aqui é que o Modelo não sabe nada sobre Views (e por que deveria?) E pode ser reutilizado em outras situações, mesmo naquelas sem Views.Você tem uma situação semelhante no seu jogo. O GameEngine normalmente conhece os GameStates. Mas o GameState não precisa saber tudo sobre o GameEngine - ele só precisa acessar certos métodos de renderização no GameEngine. Isso deve disparar um pouco de alarme em sua cabeça, dizendo que (a) o GameEngine está tentando fazer muitas coisas em uma classe e / ou (b) o GameState não precisa de todo o mecanismo do jogo, apenas da parte renderizável.
Três abordagens para resolver isso incluem:
fonte
É geralmente considerado uma boa prática ter alta coesão com baixo acoplamento. Aqui estão alguns links sobre acoplamento e coesão.
acoplamento da wikipedia
coesão da wikipedia
baixo acoplamento, alta coesão
stackoverflow sobre as melhores práticas
Ter duas classes se referindo é ter alto acoplamento. A estrutura do google guice visa obter alta coesão com baixo acoplamento por meio de injeção de dependência. Eu sugiro que você leia um pouco mais sobre o assunto e faça sua própria ligação, de acordo com o seu contexto.
fonte