digital_fate
digital_fate

Reputation: 597

How best to represent 'variant' enums?

Unfortunately I'm having to interface with some firmware that is seemingly in a constant state of flux (the USB communications protocol keeps changing, the register/address map keeps changing, the various state machines keep changing, etc.).

I've managed to implement a reasonable interface, so actually communicating with the firmware isn't too difficult.

My main problem is representing the differing state machines, of which there are about a dozen. Before they changed I just had an enumeration, such as:

public enum Component1StateType
{
    RESET        = 0,
    INITIALISING = 1,
    READY        = 2,
    WAITING      = 3
}

which I then used freely in the main code to modify the software's behaviour depending on the system state.

Now I have the problem whereby the newer firmware's Component1StateType has changed to something like this:

public enum Component1StateType
{
    RESET        = 0,
    INITIALISING = 1,
    INITIALISED  = 2,
    READY        = 3,
    ERROR        = 4,
    STANDBY      = 5,
    WAITING      = 6
}

which will then break all the previous state-handling code in the main program, which must of course support all the different versions of firmware.

I'm struggling to come up with a good way of representing these differing state machines in a way that means the main code won't be littered with things like:

if (stateMachineVersion == 1)
{
    //code branch for version 1
}
else if(stateMachineVersion == 2)
{
    //code branch for version 2
}
...
...

Any ideas on how best to deal with these ever-changing states?

Upvotes: 2

Views: 1128

Answers (3)

Stephen Kennedy
Stephen Kennedy

Reputation: 21548

I have some real world experience of this as I maintain a service which has to handle thousands of concurrent connections from a variety of telematics tracking units ("modems"), some of which are variations on a theme from the same manufacturer and some of which have wildly differering protocols. When I first met this code most of the work was done in a single class which was, if I recall correctly, around 10,000 lines of code! I refactored it into an OOP model some years ago and it has been much, much easier to work with ever since.

I have an abstract base class for a Modem and derived classes of varying levels for each modem type. Also in play are several interfaces which represent various behaviours. I would suggest you refactor into something like the following:

internal abstract class Device // Generic name here as I don't know what kind of device you are talking to {
    // Common code here
    // abstract and/or virtual members here for the different behaviours in the various firmware versions        
}

internal sealed class DeviceFirmwareA : Device
{
     // Private, firmware-specific enumerations here
     // Overrides here
}

internal sealed class DeviceFirmwareB : Device
{
     // Private, firmware-specific enumerations here
     // Overrides here
}

Of course you will also need:

  • Firmware detection and instantiation of the appropriate "decoder" class
  • You might need to do some mapping or make some other changes if, say, Component1StateType needs to be public.

I'm sorry I'm vague on the additional steps as I'd need to see the existing code in question but if you go down this route I'm sure you can work it out!

Upvotes: 1

user2160375
user2160375

Reputation:

Hmm, my proposal: Add missing states after stock value of the old version:

public enum Component1StateType
{
    RESET        = 0,
    INITIALISING = 1,
    READY        = 2,
    WAITING      = 4,
    INITIALISED  = 5,
    // etc
}

It won't broke old version.

Next, prepare attribute, that could be used in such way:

public enum Component1StateType
{
    RESET        = 0,
    INITIALISING = 1,
    [StateMapping(MachineStateVersion = 2, Value = 3)]
    READY        = 2,
    [StateMapping(MachineStateVersion = 2, Value = 4)]
    WAITING      = 3,
    [StateMapping(MachineStateVersion = 2, Value = 2)]
    INITIALISED  = 5,
    // etc
}

The attribute could be used multiple times for various range of machnie state version. Then prepare extension method:

public static bool IsInState(this Enum enum, int machineStateVersion, Component1StateType value)
{
    // fetch proper mapped value via reflection from attribute basing on machineState
}

Usage:

Component1StateType currentState = // val from some;  
if (currentState.IsInState(CURRENT_MACHINE_STATE_VERSION, 
                                Component1StateType.INITIALISING)) 
{
  //
}

Pros

  1. You can easily add the further version supporting

Cons

  1. Lot of implementation
  2. A little mess can occur when there would be a lot of machine state versions with different mappings.
  3. Cannot use switch (must base on ifs)

Upvotes: 0

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726479

Make an interface for your state machine, and expose an enum at the top level without providing any numeric values to the program outside the state machine class:

interface HardwareConnector {
    Component1StateType CurrentState {get;}
    ... // Other properties and methods
}

public enum Component1StateType {
    Reset
,   Initializing
,   Initialized
,   Ready
,   Error
,   Standby
,   Waiting
}

Make different implementations of the HardwareConnector interface depending on the version of hardware that they need to support. Your code should be programming to the HardwareConnector interface. The only place where the code should "know" about class-per-hardware-version implementations is initialization, where you detect the hardware on the other end.

Inside each implementation class you can have a private enum disconnected from the Component1StateType enumeration visible at the interface level.

internal enum Component1StateTypeV1 {
    RESET        = 0,
    INITIALISING = 1,
    READY        = 2,
    WAITING      = 3
}

internal enum Component1StateTypeV2 {
    RESET        = 0,
    INITIALISING = 1,
    INITIALISED  = 2,
    READY        = 3,
    ERROR        = 4,
    STANDBY      = 5,
    WAITING      = 6
}

Internals of the class use private enum values for doing their work, and then translate its private value to the public value in the implementation of the State getter:

public Component1StateTypeV {
    get {
        switch (internalState) {
            case Component1StateTypeV1.RESET : return Component1StateTypeV.Reset;
            case Component1StateTypeV1.INITIALISING : return Component1StateTypeV.Initializing;
            case Component1StateTypeV1.READY : return Component1StateTypeV.Ready;
            case Component1StateTypeV1.WAITING : return Component1StateTypeV.Waiting;
        }
        throw new InvalidOperationException();
    }
}

Upvotes: 2

Related Questions