I'm working on a piece of software which generates configuration data for certain hardware and currently needs to be adapted each time the hardware is released in a new version by an external company (this happens around twice a year). My current task is to introduce a plugin interface so the whole application doesn't have to be recompiled and re-released each time.

Currently, injecting new functionality - or replacing default functionality by adapted one - is already possible at runtime. However, there is one central enum which has all the different versions in it and prevents large parts of code from being separated from the executable.

The enum looks somewhat like this:

enum class HardwareVersion
{
    Stage1_Base,           // corresponds to version 5.11.37
    Stage1_Extensions,     // corresponds to version 5.12.01
    Stage2_Base,           // corresponds to version 8.00.00
    Stage2_ABC_Extensions, // corresponds to version 8.03.20
    Stage2_DEF_Extensions  // corresponds to version 9.01.00
}

Note: The names like "Stage 1 Base" are terms which are usually used when discussing with project management or the customer, hence they were used in code as well.

The users of the software need to view and modifiy data for every possible stage and generate the respective configuration data for the hardware version of interest (They are using various hardware versions simultaneously). Because of this, the software has several code parts similar to the following (theoretical code so you get the idea):

// This would be in a plugin
void addContent( HardwareVersion currentStage ) /* override */
{
    if (currentStage < Stage2_Base) return;

    // add stage 2 content
    ...
}

and

// This would be in a plugin as well
void initialize() /* override */
{
    _someFactory->register( HardwareVersion::Stage2_Base, new Stage2SubcontrolFactory() );
}

// This would be somewhere in the core application
std::shared_ptr<Subcontrol> createSubcontrol( HardwareVersion currentStage )
{
    return getFactory( currentStage )->createSubcontrol();
}

Now we could simply replace the enum by a typedef int and lose all type safety etc. We could also use GUIDs and lose all readability and make debugging really hard.

Both solutions feel really bad. This (finally) leads to the question:

What should you replace an enumeration with if values are to be provided by plugins?

[Edit:] To make this less subjective: The two major critera for solutions are high maintainability (this kinda implies readability for me) and high stability when adding new plugins.

有帮助吗?

解决方案

After sleeping over the question for a night and thinking about the various comments so far, I found the following solution to fit best for our needs:

Create a HardwareVersion C-style POD (*) struct like this:

struct IMPORT_EXPORT HardwareVersion
{
public:
    /// The first part of the version
    unsigned int Major;
    /// The second part of the version
    unsigned int Minor;
    /// The third part of the version
    unsigned int Release;
    /// A readable name mainly used for logging and debugging
    char* ReadableName;
}

Additionally, A Cpp Wrapper with functions like comparing, hash support, version tuple string ("XX.YY.ZZ" where XX = Major, YY = Minor and ZZ = Release; Note that by definition, none of the version parts can exceed 99 in this case) will be added, but not exposed through the plugin interface.

This has the following advantages:

  • The executable can be implemented to not allow plugins which provide HardwareVersion objects which are equal to already known ones OR it can be implemented to always use the newest one, so patches can be realized through plugins as well (This will most likely not be decided by me).
  • The plugins can specify which plugins they depend on through the version number (e.g. if Stage2_ABC_Extensions has a factory which provides a subclass of an object of Stage2_Base, the plugins could be initialized according to their dependency chain).
  • Code which shall be executed only above or below a certain stage is still readable
  • The application can be extended by only adding a plugin, without additional configuration required in the user environment

(*) POD to increase chances of being able to write a plugin with a different compiler (though I am aware this is not guaranteed)

其他提示

I would say that rather than use an actual enumeration, it would be advisable to have each plugin use a set of offsets from some base value. The base value for each plugin can be supplied when the plugin is loaded.

This makes it fairly easy to maintain a unique set of values across the entire system, while still allowing each plugin to be developed in isolation, without any development-time dependency.

许可以下: CC-BY-SA归因
scroll top