7

Here is my problem: I want to read input from different HID devices such as a gamepad, racing well, joystick, etc. Pretty much any game controller. The issue is that they all have different inputs.

The gamepad has buttons, switches and sticks while the racing well might have a gear stick. I managed to abstract all these different components into just 3 so instead of having a base class with all possible combinations:

abstract class Device
    {
    public Buttons Buttons;
    public Axes Axes;
    public Switches Switches;
    public GearSticks GearSticks;
    //many more
    }

I can now have:

abstract class Device
{
public Buttons Buttons;   //on or off
public Axes Axes;         //range [-100%:100%]
public Switches Switches; //multiple states
}

At first I was happy with this since it seemed to cover all possible types of input and so my class can stay closed while being open to extension via all the concrete implementations since everything can be abstracted to just 3 types of input.

BUT then I though to myself what if I'm just delaying the inevitable? What if one day I will have to add another field to my Device class? It does not support something like a trackball!

Is there a way I can future proof this class? The way I see it I would end up with something like this:

public Device1 : Device
{
//buttons
//trackball
}

public Device2 : Device
{
//Switch
//Axis
}

public Device3 : Device
{
//trackball
//switch
}

And I would have to keep adding properties to my base class every time there is something new to implement.

  • The only way to completely future-proof such a class is to store key-value pairs as strings and pass values around your classes with some sort of typeless protocol like Protocol Buffers. But HID is more specific than that. What platform are you on? There are several helper libraries that can ease this process; here is one for the .NET Framework: https://github.com/mikeobrien/HidLibrary. Or this one: https://github.com/jcoenraadts/hid-sharp – Robert Harvey Sep 04 '18 at 17:20
  • This is an excellent software engineering question, and I have no idea why you got a downvote for it. – Doc Brown Sep 04 '18 at 17:48
  • @RobertHarvey When you said key-value pairs something started to make sense in my mind but what do you mean by typeless protocol? I'm on the UWP platform. – Mihai Bratulescu Sep 04 '18 at 17:54
  • A typeless protocol is one that works entirely on string pairs, like the one that an URL uses: `parameter1=value1&parameter2=value2`, etc. – Robert Harvey Sep 04 '18 at 18:37

2 Answers2

6

I am pretty sure this can be done by introducing a concept like an abstract InputChannel, and devices having a configurable list of input channels. An input channel will have a name, a type, maybe some meta data, and it need to be able to produce some "state" which fits to that type. There might be predefined channels like a button, an axe or a switch, or some new channel you currently don't know (but might be added later by introducing a new InputChannel child class).

This way, a device will become some kind of meta model, and you will also need a way to manage the device's states, which have to correspond to the list of input channels of the device.

However, that kind of generic approach has a certain risk of overengineering, also known as Inner-platform effect. For example, it may not be easy to add specific functionality to such a generic device, or specific events, or interactions between different input channels. It may also be harder to use and understand for a user of your generic device library.

Note that it is not always beneficial to create the most abstract solution possible. Changing requirements in hardware do typically need way more effort to be implemented in the hardware itself than in corresponding software, so often it is better to stick to a more specific solution in software, and change the software when necessary.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • An `InputChannel` can work, all I need it to do is to update it's state and raise an `onChangedEvent`. But you might be right about the over engineering... I will keep this in mind. Do you think adding another field to a class every now and then is acceptable? – Mihai Bratulescu Sep 04 '18 at 18:07
  • 1
    @MihaiBratulescu: it depends on what kind of software you write - some game framework for yourself or your team, where you know the use cases and can react accordingly when requirements change, or some generic framework like Qt or the .Net framework which will be used by hundreds of thousands of devs in unforeseeable situations. For the latter, a very generic and SOLID solution is much more important than for the former. – Doc Brown Sep 04 '18 at 18:13
  • **the signpost up ahead - your next stop, the Twilight Zone**: *"Support is VERY limited for this library. It is almost impossible to troubleshoot issues with so many devices and configurations."* This is from [the hidLibrary @RobertHarvey refs](https://softwareengineering.stackexchange.com/questions/377919/difficulty-making-this-class-open-closed#comment831193_377919). Loudly hinting what one is in for grabbing a-hold of [a unified field theory Tar Baby](https://en.wikipedia.org/wiki/Tar-Baby) – radarbob Sep 07 '18 at 23:09
3

The idea behind the open-closed principle is that you are less likely to break existing functionality if you implement new functionality through inheritance rather than modification of an existing class. And you can do so, by inheriting from your Hid. In a year and a couple of months you can create a Hid2020 that inherits from Hid and adds support for the trackball that will be invented in Q4 of 2019. After the invention and popularization of the squeeze detector in 2023 you can create a Hid2024 class that descends from Hid2019.

That would be the defensive approach. But it would also be a bit sloppy from a clean design perspective. In your case I would not lose any sleep over violating O and just change the base class as the world around you changes. It does not seem like the implementation for trackball or any other new type of control will impact the way you handle button presses or switch status changes now.

Martin Maat
  • 18,218
  • 3
  • 30
  • 57
  • Ditto on the 2nd paragraph. Coherent design is the thing. And speaking of coherent; O/C has already served it's purpose here, simply considering O/C makes one realize the class will be opened (modified) *too often* probably due to inadequate design. IMO this is the raison d'etre of the Open/Closed Principle. – radarbob Sep 07 '18 at 23:33