There is a lot of valid approaches to solve your problem. Basile Starynkevitch proposed a “zero-bureaucracy” approach which leaves you with a simple interface and relies on the programmer using appropriately the interface. While I like this approach, I will present another one which has more eingineering but allows the compiler to catch some errors.
Identify the various states your device can be in, as
Configured and so on. The list has to be finite.¹
For each state, define a
struct holding the necessary additional
information relevant to that state, e.g.
DeviceStarted and so on.
Pack all treatments in one object
DeviceStrategy where methods use
structures defined in 2. as inputs and outputs. Thus, you may have
DeviceStarted DeviceStrategy::start (DeviceUninitalised dev) method
(or whatever the equivalent might be according to your project conventions).
With this approach, a valid program must call some methods in the sequence enforced by the method prototypes.
The various states are unrelated objects, this is because of the substitution principle. If it is useful to you to have these structures share a common ancestor, recall that the visitor pattern can be used to recover the concrete type of the instance of an abstract class.
While I described in 3. a unique
DeviceStrategy class, there is situations where you may want to split the functionality it provides across several classes.
To summarise them, the key points of the design I described are:
Because of the substitution principle, objects representing device states
should be distinct and not have special inheritance relations.
Pack device treatments in startegy objects rather than in the objects
representing devices themselves, so that each device or device state
sees only itself, and the strategy sees all of them and express possible
transitions between them.
I would swear I saw once a description of a telnet client implementation
following these lines, but I was not able to find it again. It would have
been a very useful reference!
¹: For this, either follow your intuition or find the equivalence classes of methods in your actual implementation for the relation “method₁ ~ method₂ iff. it is valid to use them on the same object” — assuming you have a big object encapsulating all the treatments on your device. Both methods of listing states give fantastic results.