14

For example, suppose you have a console game program, which has all kinds of input/output methods to and from the console. Would it be smart to keep them all in a single inputOutput class or break them down to more specific classes like startMenuIO, inGameIO, playerIO, gameBoardIO, etc. such that each class has about 1-5 methods?

And on the same note, if it's better to break them down, would it be smart to place them in a IO namespace thus making calling them a little more verbose, e.g: IO.inGame etc.?

3

4 Answers 4

7

Update (recap)

Since I've written a rather verbose answer, here's what it all boils down to:

  • Namespaces are good, use them whenever it makes sense
  • Using inGameIO and playerIO classes would likely constitute a breach of the SRP. It likely means you're coupling the way you handle IO with the application logic.
  • Have a couple of generic IO classes, that are used (or sometimes shared) by handler classes. These handler classes would then translate the raw input into a format your application logic can make sense of.
  • Same goes for the output: this can be done by fairly generic classes, but pass the game state through a handler/mapper object that translates the internal game state into something the generic IO classes can handle.

I think you're looking at this in the wrong way. You're separating out the IO in function of the components of the application, whereas - to me- it makes more sense to have separate IO classes based on the source, and "type" of IO.

Having some base/generic KeyboardIO classes MouseIO to start off with, and then based on when and where you need them, have subclasses that handle said IO differently.
For example, text input is something you probably want to handle differently to in-game controls. You'll find yourself wanting to map certain keys differently depending on each use case, but that mapping isn't part of the IO itself, it's how you're handling the IO.

Sticking to the SRP, I'd have a couple of classes that I can use for keyboard IO. Depending on the situation, I'll probably want to interact with these classes differently, but their only job is to tell me what the user is doing.

I'd then inject these objects into a handler object that would either map the raw IO onto something that my application logic can work with (eg: user presses "w", the handler maps that onto MOVE_FORWARD).

These handlers, in turn are used to make the characters move, and draw the screen accordingly. A gross oversimplification, but the gist of it is this kind of structure:

[ 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

What we have now is a class that is responsible for the keyboard IO in its raw form. Another class that translates this data into something the game engine can actually make sense of, this data is then used to update the state of all of the components involved, and finally, a separate class will take care of the output to the screen.

Every single class has a single job: handling keyboard input is done by a class that doesn't know/care/has to know what the input it's processing means. All it does is know how to get the input (buffered, unbuffered, ...).

The handler translates this into an internal representation for the rest of the application to make sense of this info.

The game engine takes the data that was translated, and uses it to notify all of the relevant components that something is going on. Each of these components do just one thing, whether that be collision checks, or character animation changes, it doesn't matter, that's down to each individual object.

These objects then relay their state back, and this data is passed to Game.Screen, which is in essence an inverse IO handler. It maps the internal representation onto something the IO.Screen component can use to generate the actual output.

7
  • Since it's a console application there's no mouse, and printing the messages or the board is tightly linked with the input. In your example, are the IO and game namespaces or classes with subclasses?
    – shinzou
    Apr 20, 2016 at 16:01
  • @kuhaku: They're namespaces. The gist of what I'm saying is that, if you choose to create subclasses based on what part of the application you're in, you're effectively tightly coupling the basic IO functionality with your application logic. You'll end up with classes that are responsible for IO in function of the application. That, to me, sounds like a violation of the SRP. As for names vs namespaced classes: I tend to prefer namespaces 95% of the time Apr 20, 2016 at 16:09
  • I've updated my answer, to summarize my answer Apr 20, 2016 at 16:15
  • Yeah that's actually another problem I'm having (coupling IO with the logic), so you actually recommend to separate input from output?
    – shinzou
    Apr 20, 2016 at 16:16
  • @kuhaku: That really depends on what you're doing, and how complex the input/output stuff is. If the handlers (translating game state vs raw input/output) differ too much, then you'll probably want to separate the input and output classes, if not, a single IO class is fine Apr 20, 2016 at 16:23
24

The single responsibility principle can be tricky to understand. What I've found useful is to think of it like how you write sentences. You don't try to cram a lot of ideas into a single sentence. Each sentence should state one idea clearly and defer the details. For example, if you wanted to define a car, you would say:

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

Then you would define things like "vehicle", "road", "wheels", etc. separately. You wouldn't try to say:

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.

Likewise, you should try to make your classes, methods, etc, state the central concept as simply as possible and defer the details to other methods and classes. Just like with writing sentences, there is no hard rule as to how big they should be.

3
  • So like with defining the car with a few words instead of many, then defining small specific but similar classes is fine?
    – shinzou
    Apr 20, 2016 at 16:09
  • 2
    @kuhaku: Small is good. Specific is good. Similar is fine as long as you keep it DRY. You are trying to make your design easy to change. Keeping things small and specific helps you know where to make changes. Keeping it DRY helps avoid having to change a lot of places. Apr 20, 2016 at 16:22
  • 6
    Your second sentence looks like "Damn, teacher said this needs to be 4 pages... 'generates motive power' it is!!"
    – corsiKa
    Apr 20, 2016 at 22:33
2

I'd say that the best way to go is to keep them in separate classes. Small classes are not bad, in fact most of the time they're a good idea.

Regarding your specific case I think that having the separated can help you change the logic of any of those specific handlers without affecting the others and, if necessary, it would be easier for you to add new input/output method if it came to it.

0

The Single Responsibility Principal states that a class should only have one reason to change. If your class has multiple reasons to change, then you can split it out into other classes and utilize composition to eliminate this problem.

To answer your question, I have to ask you a question: Does your class have only one reason to change? If not, then don't be afraid to keep adding more specialized classes until each one only has one reason to change.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.