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).