Atualização (recapitulação)
Desde que eu escrevi uma resposta bastante detalhada, aqui está o que tudo se resume a:
- Os namespaces são bons, use-os sempre que fizer sentido
- O uso
inGameIO
e as playerIO
classes provavelmente constituiriam uma violação do SRP. Provavelmente significa que você está acoplando a maneira como lida com as E / S com a lógica do aplicativo.
- Tenha algumas classes de E / S genéricas, que são usadas (ou às vezes compartilhadas) pelas classes de manipulador. Essas classes de manipuladores traduzem a entrada bruta para um formato que sua lógica de aplicativo possa entender.
- O mesmo vale para a saída: isso pode ser feito por classes bastante genéricas, mas passe o estado do jogo através de um objeto manipulador / mapeador que traduza o estado interno do jogo em algo que as classes de E / S genéricas possam manipular.
Eu acho que você está vendo isso da maneira errada. Você está separando o IO em função dos componentes do aplicativo, enquanto - para mim - faz mais sentido ter classes de IO separadas com base na origem e no "tipo" de IO.
Tendo algumas KeyboardIO
classes básicas / genéricas MouseIO
para começar e, depois, com base em quando e onde você precisar delas, tenha subclasses que lidam com o referido IO de maneira diferente.
Por exemplo, a entrada de texto é algo que você provavelmente deseja manipular de maneira diferente dos controles do jogo. Você vai querer mapear determinadas chaves de maneira diferente, dependendo de cada caso de uso, mas esse mapeamento não faz parte do próprio IO, é como você está lidando com o IO.
Seguindo o SRP, eu teria algumas classes que posso usar para E / S de teclado. Dependendo da situação, provavelmente desejarei interagir com essas classes de maneira diferente, mas o único trabalho deles é me dizer o que o usuário está fazendo.
Eu injetava esses objetos em um objeto manipulador que mapeia a IO bruta para algo com o qual minha lógica de aplicativo possa trabalhar (por exemplo: o usuário pressiona "w" , o manipulador mapeia isso MOVE_FORWARD
).
Esses manipuladores, por sua vez, são usados para fazer os personagens se moverem e desenharem a tela de acordo. Uma simplificação grosseira, mas a essência é esse tipo de estrutura:
[ IO.Keyboard.InGame ] // generic, if SoC and SRP are strongly adhered to, changing this component should be fairly easy to do
||
==> [ Controls.Keyboard.InGameMapper ]
[ Game.Engine ] <- Controls.Keyboard.InGameMapper
<- IO.Screen
<- ... all sorts of stuff here
InGameMapper.move() //returns MOVE_FORWARD or something
||
==> 1. Game.updateStuff();//do all the things you need to do to move the character in the given direction
2. Game.Screen.SetState(GameState); //translate the game state (inverse handler)
3. IO.Screen.draw();//generate actual output
O que temos agora é uma classe responsável pelo IO do teclado em sua forma bruta. Outra classe que traduz esses dados em algo que o mecanismo do jogo pode realmente entender, esses dados são usados para atualizar o estado de todos os componentes envolvidos e, finalmente, uma classe separada cuidará da saída da tela.
Cada classe tem um único trabalho: o tratamento da entrada do teclado é feito por uma classe que não sabe / se importa / precisa saber o significado da entrada que está processando. Tudo o que faz é saber como obter a entrada (em buffer, sem buffer, ...).
O manipulador converte isso em uma representação interna para o restante do aplicativo, a fim de entender essas informações.
O mecanismo de jogo pega os dados que foram traduzidos e os utiliza para notificar todos os componentes relevantes de que algo está acontecendo. Cada um desses componentes faz apenas uma coisa, sejam verificações de colisão ou alterações na animação de personagens, não importa, isso depende de cada objeto individual.
Esses objetos retransmitem seu estado de volta e esses dados são passados para Game.Screen
, o que é essencialmente um manipulador de E / S inverso. Ele mapeia a representação interna em algo que o IO.Screen
componente pode usar para gerar a saída real.