Quão específico deve ser o padrão de Responsabilidade Única para as aulas?

14

Por exemplo, suponha que você tenha um programa de jogo de console, que tem todos os tipos de métodos de entrada / saída para e do console. Seria inteligente mantê-los todos em uma única classe inputOutput ou dividi-los em classes mais específicas, como startMenuIO , inGameIO , playerIO , gameBoardIO , etc., para que cada classe tenha cerca de 1 5 métodos?

E na mesma nota, se é melhor dividi-los, seria inteligente colocá-los em um espaço de nomes IO , tornando-os um pouco mais detalhados, por exemplo: IO.inGame etc.?

    
por shinzou 20.04.2016 / 11:46
fonte

4 respostas

7

Atualizar (recapitular)

Desde que 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 de classes inGameIO e playerIO provavelmente constituiria uma violação do SRP. Isso provavelmente significa que você está acoplando a maneira como lida com o IO com a lógica do aplicativo.
  • Ter algumas classes genéricas de E / S, que são usadas (ou às vezes compartilhadas) por classes manipuladoras. Essas classes manipuladoras, então, 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 razoavelmente genéricas, mas passa o estado do jogo por um objeto manipulador / mapeador que traduz o estado interno do jogo em algo que as classes genéricas de E / S podem manipular.

Eu acho que você está olhando para isso de 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 "tipo" de IO.

Ter um pouco de base / genérica KeyboardIO classes MouseIO para começar e, em seguida, com base em quando e onde você precisar deles, ter subclasses que manipulem esse IO de maneira diferente.
Por exemplo, a entrada de texto é algo que você provavelmente deseja lidar de maneira diferente com controles no jogo. Você se verá querendo mapear certas 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.

Observando o SRP, eu tenho algumas aulas que posso usar para o IO do teclado. Dependendo da situação, provavelmente vou querer interagir com essas classes de maneira diferente, mas o único trabalho delas é me dizer o que o usuário está fazendo.

Eu então injetaria esses objetos em um objeto de manipulador que mapearia o IO bruto para algo com o qual minha lógica de aplicativo pode trabalhar (por exemplo: o usuário pressiona "w" , o manipulador mapeia em MOVE_FORWARD ).

Esses manipuladores, por sua vez, são usados para fazer os personagens se moverem e desenhar a tela de acordo. Uma simplificação grosseira, mas a essência disso é 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 que é 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 para a tela. / p>

Cada classe tem um único trabalho: manipular a entrada do teclado é feito por uma classe que não sabe / care / precisa saber o que significa a entrada que está processando. Tudo o que ele faz é saber como obter a entrada (buffer, unbuffered, ...).

O manipulador traduz isso em uma representação interna para o resto do aplicativo para fazer sentido desta informação.

O mecanismo de jogo pega os dados que foram traduzidos e os usa 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 de animação de personagem, não importa, isso é para cada objeto individual.

Esses objetos retransmitem seu estado de volta e esses dados são passados para Game.Screen , que é, em essência, um manipulador de IO inverso. Ele mapeia a representação interna para algo que o componente IO.Screen pode usar para gerar a saída real.

    
por 20.04.2016 / 15:47
fonte
23

O princípio da responsabilidade única pode ser complicado de entender. O que eu acho útil é pensar nisso como você escreve frases. Você não tenta empinar muitas idéias em uma única frase. Cada sentença deve indicar uma ideia claramente e adiar os detalhes. Por exemplo, se você quisesse definir um carro, você diria:

A road vehicle, typically with four wheels, powered by an internal combustion engine.

Então você definiria coisas como "veículo", "estrada", "rodas", etc. separadamente. Você não tentaria dizer:

A vehicle for transporting people on a thoroughfare, route, or way on land between two places that has been paved or otherwise improved to allow travel that has four circular objects that revolve on an axle fixed below the vehicle and is powered by an engine that generates motive power by the burning of gasoline, oil, or other fuel with air.

Da mesma forma, você deve tentar fazer com que suas classes, métodos, etc, declarem o conceito central da maneira mais simples possível e adiem os detalhes a outros métodos e classes. Assim como com a redação de frases, não existe uma regra rígida quanto ao tamanho que devem ser.

    
por 20.04.2016 / 16:31
fonte
2

Eu diria que o melhor caminho a percorrer é mantê-los em turmas separadas. Turmas pequenas não são ruins, na verdade, na maioria das vezes, são uma boa ideia.

Em relação ao seu caso específico, acho que ter o separado pode ajudá-lo a mudar a lógica de qualquer manipulador específico sem afetar os outros e, se necessário, seria mais fácil adicionar novo método de entrada / saída se ele viesse para isso.

    
por 20.04.2016 / 11:52
fonte
0

O Responsável Único afirma que uma classe deve ter apenas um motivo para mudar. Se a sua turma tiver vários motivos para mudar, você poderá dividi-la em outras turmas e utilizar a composição para eliminar essa problema.

Para responder à sua pergunta, tenho que fazer uma pergunta: sua turma tem apenas um motivo para mudar? Caso contrário, não tenha medo de continuar adicionando aulas mais especializadas até que cada uma tenha apenas um motivo para mudar.

    
por 20.04.2016 / 18:22
fonte