Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

The task is to configure a piece of hardware within the device, according to some input specification. This should be achieved as follows:

1) Collect the configuration information. This can happen at different times and places. For example, module A and module B can both request (at different times) some resources from my module. Those 'resources' are actually what the configuration is.

2) After it is clear that no more requests are going to be realized, a startup command, giving a summary of the requested resources, needs to be sent to the hardware.

3) Only after that, can (and must) detailed configuration of said resources be done.

4) Also, only after 2), can (and must) routing of selected resources to the declared callers be done.


A common cause for bugs, even for me, who wrote the thing, is mistaking this order. What naming conventions, designs or mechanisms can I employ to make the interface usable by someone who sees the code for the first time?

share|improve this question
    
Stage 1 is better called discovery or handshake ? –  rwong Aug 22 at 16:09
1  
Temporal coupling is an anti-pattern and should be avoided. –  Snowman Aug 22 at 20:30
1  
The title of the question makes me think you might be interested in the step builder pattern. –  Joshua Taylor Aug 23 at 12:36

9 Answers 9

It's a redesign but you can prevent misuse of many APIs but not having available any method that shouldn't be called.

For example, instead of first you init, then you start, then you stop

Your constructor inits an object that can be started and start creates a session that can be stopped.

Of course if you have a restriction to one session at a time you need to handle the case where someone tries to create one with one already active.

Now apply that technique to your own case.

share|improve this answer
    
zlib and jpeglib are two examples that follow this pattern for initialization. Still, plenty of documentations are necessary to teach the concept to developers. –  rwong Aug 22 at 11:21
5  
This is exactly the right answer: if order matters, each function returns a result that can then be called on to perform the next step. The compiler itself is able to enforce the design constraints. –  Snowman Aug 22 at 20:31
2  
This is similar to the step builder pattern; only present the interface that makes sense at a given phase. –  Joshua Taylor Aug 23 at 12:42
    
@JoshuaTaylor my answer is a step builder pattern implementation :) –  Silviu Burcea Aug 24 at 5:42
    
@SilviuBurcea Your answer isn't a step builder implementatio, but I'll comment on it rather than here. –  Joshua Taylor Aug 24 at 17:54

You can have the startup method return an object that is a required parameter to the configuration:

Resource *MyModule::GetResource();
MySession *MyModule::Startup();
void Resource::Configure(MySession *session);

Even if your MySession is just an empty struct, this will enforce through type safety that no Configure() method can be called before the startup.

share|improve this answer
    
+1 this is the most flexible way to do it. –  Mehrdad Aug 22 at 20:05
    
What stops someone from doing module->GetResource()->Configure(nullptr)? –  svick Aug 31 at 23:40
    
@svick: Nothing, but you must explicitly do this. This approach tells you what it expects and bypassing that expecation is a conscious decision. As with most programming languages, no one's preventing you from shooting yourself in the foot. But it's always good by an API to clearly indicate that you are doing so ;) –  Michael Barth Oct 1 at 10:35
    
+1 looks great and simple. However, I can see a problem. If I have objects a, b, c, d, then I can start a, and using it's MySession to attempt to use b as an already started object, while in reality it is not. –  Vorac Oct 15 at 7:24

Building on the Answer of Cashcow - why do you have to present a new Object to the caller, when you can just present a new Interface ? Rebrand-Pattern:

class IStartable     { public: virtual IRunnable      start()     = 0; };
class IRunnable      { public: virtual ITerminateable run()       = 0; };
class ITerminateable { public: virtual void           terminate() = 0; };

You can also let ITerminateable implement IRunnable, if a session can be run multiple times.

Your object:

class Service : IStartable, IRunnable, ITerminateable
{
  public:
    IRunnable      start()     { ...; return this; }
    ITerminateable run()       { ...; return this; }
    void           terminate() { ...; }
}

// And use it like this:
IStartable myService = Service();

// Now you can only call start() via the interface
IRunnable configuredService = myService.start();

// Now you can also call run(), because it is wrapped in the new interface...

In this way you can only call the right methods, since you have only the IStartable-Interface in the beginning and will get the run() Method only accessible when you have called start(); From the outside it looks like a pattern with multiple classes and Objects, but the underlying class stays one class, which is always referenced.

share|improve this answer
1  
What is the advantage of having just one underlying class instead of several? As this is the only difference with the solution I proposed, I would be interested in this particular point. –  Michael Grünewald Aug 22 at 13:37
    
@MichaelGrünewald It's not necessary to implement all the interfaces with one class, but for a configuration-type object, it may be the simplest implementation technique to share the data between instances of the interfaces (i.e., because it's shared by virtue of being the same object). –  Joshua Taylor Aug 23 at 12:38
    
This is essentially the step builder pattern. –  Joshua Taylor Aug 23 at 12:38
    
@JoshuaTaylor Sharing data among instances of the interface is twofold: while it might be easier to implement, we have to be careful not accessing “undefined state” (like accessing the client address of a non-connected server). As the OP set emphasis on interface usability, we can judge the two approaches equal. Thnak you for quoting the “step builder pattern” BTW. –  Michael Grünewald Aug 23 at 13:20
    
@MichaelGrünewald If you only interact with the object through the particular interface that's specified at a given point, there shouldn't be any way (without casting, etc.) to access that state. –  Joshua Taylor Aug 23 at 13:24

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.

  1. Identify the various states your device can be in, as Uninitialised, Started, Configured and so on. The list has to be finite.¹

  2. For each state, define a struct holding the necessary additional information relevant to that state, e.g. DeviceUninitialised, DeviceStarted and so on.

  3. Pack all treatments in one object DeviceStrategy where methods use structures defined in 2. as inputs and outputs. Thus, you may have a 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:

  1. Because of the substitution principle, objects representing device states should be distinct and not have special inheritance relations.

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

share|improve this answer
1  
Rather than defining separate structs, it may suffice to define the necessary interfaces that an object at each phases should present. Then it's the step builder pattern. –  Joshua Taylor Aug 23 at 12:40

Use a builder-pattern.

Have an object which has methods for all the operations you mentioned above. However, it doesn't perform these operations right away. It just remembers each operation for later. Because the operations aren't executed right away, the order in which you pass them to the builder doesn't matter.

After you defined all the operations on the builder, you call an execute-method. When this method is called, it performs all the steps you listed above in the correct order with the operations you stored above. This method is also a good place to perform some operation-spanning sanity-checks (like trying to configure a resource which wasn't set up yet) before writing them to the hardware. This might save you from damaging the hardware with a nonsensical configuration (in case your hardware is susceptible to this).

share|improve this answer

You just need to document correctly how the interface is used, and give a tutorial example.

You may also have a debugging library variant which does some runtime checks.

Perhaps defining and documenting correctly some naming conventions (e.g. preconfigure*, startup*, postconfigure*, run*....)

BTW, a lot of existing interfaces follow a similar pattern (e.g. X11 toolkits).

share|improve this answer
    
A state transition diagram, similar to the Android application activity lifecycle, may be necessary to convey the information. –  rwong Aug 22 at 16:12

This is indeed a common and insidious kind of error, because compilers can only enforce syntax conditions, while you need your client programs to be "grammatically" correct.

Unfortunately, naming conventions are almost entirely ineffective against this kind of error. If you really want to encourage people not to do ungrammatical things, you should pass out a command object of some kind that must be initialized with values for the preconditions, so that they can't perform the steps out of order.

share|improve this answer
    
Do you mean something like this? –  Vorac Aug 22 at 11:06
public class Executor {

private Executor() {} // helper class

  public void execute(MyStepsRunnable r) {
    r.step1();
    r.step2();
    r.step3();
  }
}

interface MyStepsRunnable {

  void step1();
  void step2();
  void step3();
}

Using this pattern you are sure that any implementor will execute in this exact order. You can go one step further and make an ExecutorFactory which will build Executors with custom execution paths.

share|improve this answer
    
In another comment you called this a step builder implementation, but it's not. If you have an instance of MyStepsRunnable, then you could call step3 before step1. A step builder implementation would be more along the lines of ideone.com/UDECgY. The idea is only get that something with a step2 by running step1. Thus you're forced to call methods in the right order. E.g., see stackoverflow.com/q/17256627/1281433. –  Joshua Taylor Aug 24 at 18:00
    
You can convert it to an abstract class with protected methods(or even default) to restrict the way it can be used. You'll be forced to use the executor, but I have that there might be a flaw or two with the current implementation. –  Silviu Burcea Aug 25 at 3:57
    
That still doesn't make it a step builder. In your code, there's nothing that a user can do to run code between the different steps. The idea is not just to sequence code (regardless whether its public or private, or otherwise encapsulated). As your code shows, that's easy enough to do with simply step1(); step2(); step3();. The point of step builder is to give an API that exposes some steps, and to enforce the sequence in which they're called. It shouldn't prevent a programmer from doing other things between steps. –  Joshua Taylor Aug 25 at 12:24

Use a state machine to track what can and should be done, and when.

share|improve this answer
    
this reads more like a comment than an answer. See How to Answer –  gnat Aug 22 at 21:23
    
Hi @gnat. I just read your link and unfortunately it gives no further guidance an what separates an answer from a comment. maybe you could point out something specific on that page that would lead to the above being better as a comment? My answer includes two links to the definition of a state machine and a DrDobbs article on state machines in C++ the authors language of choice. Succinct doesn't necessarily equate to comment. –  Paddy3118 Aug 23 at 0:14
    
    
Hi @gnat, do you agree with my comments about your first link? What about my other points? As a courtesy I read up to the 1,2,3 of your second link and thought some of the comments applied to your first answer! If you read what you linked to this second time you might have fleshed-out your second response. It looks as if you are ignoring my points? –  Paddy3118 Aug 23 at 11:45
    
Brilliant, complaining that the form of an answer is invalid because it is a single link by using single link critiques. Ignore the moderator-wannabe. –  msw Aug 23 at 15:04

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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