5

I am writing a library in C++ that is used by customers to interface with hardware. There are many different devices that I need to support.

For the sake of simplicity, assume I have 2 Widgets, WidgetA and WidgetB, which are physical devices. Both Widgets communicate using the same protocol and share a lot in common. They both support 20 of the same generic configuration settings. However, WidgetA supports 10 unique configuration settings, and WidgetB supports a different 10 unique configuration settings. WidgetB also has a few unique functions as well.

The user of my library may know up front what Widget they are using (ex. if a user bought just 1 Widget and wanted to use my library to communicate with it, they would probably use it knowing they only have to communicate with WidgetA). However, there are other scenarios where the user would want to write an application that handles a variety of Widgets. The type of Widget would have to be determined at runtime by interrogating the actual device.

So my question is: What should the design be for this library? I was thinking of the following options:

1) Just brute force it and put all functions in a generic Widget class. The physical Widget would be checked before running a command and an error would occur if trying to use a function not supported by the device. This ends up being a lot of functions in 1 class, with no end in sight as new Widgets need to be supported in the future.

class Widget
{
    void setAddress(int);       //generic config setting
    void setUniqueOption1(int); //config only supported by physical WidgetA's
    void setUniqueOption2(int); //config only supported by physical WidgetB's
    ...                         //repeat for all config and functions supported by any Widget!
}

2) Add a separate class for each Widget (or each Widget that has unique features). This seems like a good idea, but users would have to create a generic Widget first, then determine what type of physical device they are talking to, then create the specific WidgetA class to access that functionality.

class Widget
{
    void setAddress(int);       //generic config setting
}

class WidgetA : public Widget
{
    void setUniqueOption1(int); //config only supported by physical WidgetA's
}

class WidgetB: public Widget
{
    void setUniqueOption2(int); //config only supported by physical WidgetB's
}

3) Create a pure virtual ConfigOption class. Create config classes for each Widget that contain options that the user sets up all at once. The user just uses the generic Widget class which has a function setCustomConfig which takes these custom widget config classes.

class Widget
{
    void setAddress(int);                      //generic config setting
    void setCustomConfig(const ConfigOption&); //used to set all custom config settings
}

class ConfigOption = 0;

class WidgetA_Config : public ConfigOption
{
    //user sets all the options, then sends the object to the Widget::setCustomConfig
    int optionA1;
    int optionA2;
    int optionA3;
}

class WidgetB_Config : public ConfigOption
{
    int optionB1;
    int optionB2;
}

This is my first time trying to write a good interface to interface with hardware, which is throwing a wrench in my normal logic that I use for writing code. Are any of these ideas the right approach? Or is there some design that I am missing?

In reality, there are ~30 of these Widgets, and the potential for more each day. Some are 99% similar to what I would consider the "Base Widget" while others can have these unique features (configuration and functionality).

1
  • Actually I am facing the same issue and I am using VB.NET. The solution I am trying now is interface. With an interface, the end user will see the same functions for any instruments of the same type but from different vendors. The trouble now is the 10% different configs or functions that I have to leave functions for some class blank. Hopefully this is helpful for you.
    – user164557
    Jan 15, 2015 at 8:49

1 Answer 1

5

I think you might be getting stuck trying to make your class hierarchy fit a real-world taxonomy, and that's not always the best approach.

First of all, objects are almost always created by some sort of factory in this situation. You call a probe function that returns a list of all Widgets connected to your system, already instantiated. Then you can configure it.

The model I see most often for a hardware device is a data structure of features. Take a mouse, for example. They all have at least two buttons and at least two axes, but some have much more. Rather than have a base Mouse class with derived classes like ThreeButtonWithScroll, they each have an Axes collection and a Buttons collection. The user of the class isn't allowed to add or remove an axis or button, but he can set properties on each.

Say you had some sort of weird mouse without any buttons. Its Buttons collection would just be empty, and therefore unconfigurable. You're not exposing any interfaces that aren't allowed to be used.

If your list of features is so huge that you would be only using a small portion of them on any given device, then you could create an array of Features in your Widget class. Where a Feature could be the unique things about some mice like Vibration or BacklightColor. These can also be relatively generic, like BooleanFeature("Vibration", disabled, callback).

1
  • Thanks for the response! Most of my devices have 90% of the same features. But then I have devices that have a handful of unique features, like the Vibration and BacklightColor that you mention. I'd like to keep the functions explicit to avoid the user having to rely completely on documentation. So instead of the BooleanFeature that you mention, I could maybe have each Feature be a struct. So Vibration would be a struct which is passed to Widget::setFeature() or returned from Widget::getFeature(). I think that would work, however I might end up with a lot of structs. Jan 11, 2015 at 15:38

Your Answer

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

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