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.

I tend to keep my objects consistent during their lifetime. In some cases, setting up an object requires multiple calls to different routines. For example, a connection object may operate in this way:

Connection c = new Connection();
c.setHost("http://whatever")
c.setPort(8080)
c.connect()

please note this is just a stupid example to let you understand the point. In between calls to setHost and setPort the object is inconsistent, because the Port has not been specified yet, so this code would crash

Connection c = new Connection();
c.setHost("http://whatever")
c.connect()

Meaning that it's a requisite for connect() to have previous calls to both setHost and setPort, otherwise it won't be able to operate as its state is inconsistent.

You may fix the issue with a default value, but there may be cases where no sensible default may be devised. We assume in the later example that there's no default for the port, and therefore a call to c.connect() without first calling both setHost and setPort will be an inconsistent state of the object. This, to me, points at an incorrect interface design, but I may be wrong, so I want to hear your opinion.

Do you organize your interface so that the object is always in a consistent (i.e. workable) state both before and after the call ?

Edit: Please don't try to solve the problem I gave above. I know how to solve that. My question is much broader in sense. I am looking for a design principle, officially or informally stated, regarding consistency of object state between calls.

share|improve this question
    
"Do you organize your interface so that the object is always in a consistent (i.e. workable) state". Isn't that a fundamental rule of building software? What's the alternative? Can you provide any sensible example of an object in an inconsistent state? That sounds like a simple bug that needs to be fixed. –  S.Lott Jan 13 '11 at 11:35
2  
Avoid quasi-classes. That will get you a long way towards maintaining class invariants. –  sbi Jan 13 '11 at 11:55
    
@sbi: Quasi-classes are perfectly fine in cases where what is needed is simply to have a means of collectively identifying a bunch of independent-but-related variables, though it would be helpful if compilers would facilitate the construction and interaction of immutable-class, mutable-class, and structure types holding the same data [e.g. allow code with a mutable-class instance to import data from a mutable or immutable instance, or export data to a new immutable instance]. Such types would be rather anemic, but they'd have a single responsibility (aggregate data) and fulfill it. –  supercat Jul 15 at 18:35
    
@supercat: "Quasi-classes are perfectly fine in cases where what is needed is simply to have a means of collectively identifying a bunch of independent-but-related variables." You're referring to what a struct is in C. Yeah, that's fine, but not what the article refers to. (Have you considered reading it?) –  sbi Jul 18 at 16:05
    
@sbi: I forgot to mention "add a layer of indirection"; sometimes it's necessary to have a class object which holds one value, exactly like the Thing class the article complains about. IMHO, the .NET Framework should have included something like public class SimpleHolder<T> {public T Value;} for such purposes. Use of get/set methods would be a bit silly, but encapsulating Value into its own object may be useful in a variety of circumstances, since a SimpleHolder<T> will have mutable reference semantics even if Value is a value type or immutable class type. –  supercat Jul 18 at 16:50

11 Answers 11

up vote 3 down vote accepted

I believe I understand what you are trying to get at, and there are a few different approaches you can use. Inconsistent states do create a problem in multithreaded re-entrant applications.

Dependency Injection Approach

Probably the least amount of work with the greatest return on investment would be to pair connected values any time they are changed. Using your example, we would have code that looks like this:

// when we initialize the object
Connection conn = new Connection("http://someserver", 8080);

// when we want to change something:
conn.SetDestination("http://google.com", 80);

Builder/Fluent Interface Approach

The GoF book Design Patterns outlines a pattern called a builder, and some people now call them "Fluent Interfaces". Essentially a builder is a class of object factory who's sole responsibility is to collect all the data before creating the object you want. The builder is always consistent with itself in regards to the problem it is solving. It is a separate object from the one you are trying to create. Using a builder would look like this:

Connection conn = new ConnectionBuilder().SetServer("http://someserver")
    .SetPort(8080).Connect();

This approach may be somewhat verbose, but the instance of Connection is always consistent. If Connection is immutable (another good design trait for objects of this type), then there is no way to put it in an invalid state.

Accept Inconsistency

I know this sounds like heresy considering your question, but it forces you to think differently about how you design your application. Let's face it, even with the dependency injection approach, you will have a point within the SetDestination() method where the object will be temporarily inconsistent. You can only set one variable at a time. When you exit the method, the object is consistent again.

So the point of this approach is to not get bent around the axel when you have an object that might not be consistent until some final method is called. The example you provided works to demonstrate this approach.

Connection conn = new Connection();
conn.SetServer("http://someserver");
conn.SetPort(8080);

// point where consistency matters
conn.Connect();

With this last approach, you are required to write error checking code to make sure all the preconditions of conn.Connect() are satisfied before you actually do the work. That's a good thing--you should do that anyway. JavaBeans are designed with this approach for better or for worse. The benefit is that you can bind the object to a user interface, and execute the critical code after the pertinent information is set up--instead of populating one object only to copy the values to another object.

share|improve this answer

I would redesign the interface, something like

Connection c = new Connection();
c.connect("http://whatever", 8080)

This makes sure the necessary values are present when you call that method.

I too think, that inconsistent states should be avoided as far as possible.

share|improve this answer

How about passing values to initializer?

Connection c = new Connection("http://foo", 80);
c.connect();

I, personally, use this approach when coding with ruby (and rails) as well as Objective-C (and Cocoa).

Update: I try to pass all the non default values in object initializer. This way I have consistent and working object right from the start (i.e. initialization).

share|improve this answer
2  
This coupled with making the default constructor private would mean that it's harder to create inconsistent objects. There's nothing stopping the caller supplying garbage for the arguments! You still have to put error handling in your action code. –  ChrisF Jan 13 '11 at 11:52
    
@Elimantas: I would attempt the connection in the constructor too, or at the very least, test if it's connected implicitly in every method. Calling the connect method afterward is just as much of a problem that not setting where to connect: you have an unusable object! –  Matthieu M. Jan 13 '11 at 17:56
    
@Matthieu - true, but once the connection object is setup, the channel might not be accessible, hence there might be extra code between the initialization and actual connection. –  Eimantas Jan 13 '11 at 19:45
    
I agree. See my answer for an alternative. –  Matthieu M. Jan 14 '11 at 7:17

One way would be to use a constructor:

ConnectionInterface c = new ConnectionImpl("http://whatever", 8080);
c.connect();

But then you're tied to the implementation, since interfaces cannot have constructors.

share|improve this answer

There are some interfaces which don't retain this consistency such as the OpenGL API, which uses state machines. You can expect code to create a triangle as:

glBegin(GL_TRIANGLES);

glVertex3f(-1.0f, -0.5f, -4.0f);    // lower left vertex
glVertex3f( 1.0f, -0.5f, -4.0f);    // lower right vertex
glVertex3f( 0.0f,  0.5f, -4.0f);    // upper vertex

glEnd();

Of course there's nothing stopping you from writing code like:

glEnd();
glEnd();
glBegin();
glVertex3f(0.0f, 0.0f, 0.0f);

I would argue that it's difficult if not impossible to claim that any interface can be made consistent. Take database transactions for example. You wouldn't write an interface to a database by creating a method which opens a transaction, updates a record, and commits the transaction on success otherwise rollback. If you did, you're excluding the possibility for doing two atomic updates inside one transaction rather than just one. So you'd have to create another method still.

Better to have a state machine model. You allow the programmer to screw up using such a model, but in my opinion, so long as every open has a close, every connect has a disconnect and so long as any attempts to do an action outside of its state would throw an exception, I think it's perfectly acceptable.

In your particular example, I would argue that it isn't appropriate to use a state machine engine simply because it doesn't serve any purpose. In general you should be as consistent as possible, so long as it suits your needs.

share|improve this answer
    
This is a very good example. Thanks –  Stefano Borini Jan 13 '11 at 12:12

As Bart van Ingen Schenau points out in his comment there are three cases:

Case 1 - The user does not provide all required information.

This is handled by what Eimantas suggests and I would endorse that approach, you still need to put code into your methods that check the state of the object as there is no guarantee that the user has entered valid data, which is

Case 2 - The user provides clearly bogus data.

For example, you could have:

Connection c = new Connection("hello, world", 0);
c.connect();

which would still fail.

You therefore need code to check the state and raise an error or exception if the object isn't in a valid state.

Case 3 - The user provides plausible data.

This is harder to check for and usually requires that your methods have suitable error handling in them. For example, you could have:

Connection c = new Connection("http://www.invaliddomain.com", 80);
c.connect();

This looks OK, but without actually making the call you can't know whether the data is valid or not.

share|improve this answer
    
There are three cases to consider: 1. The user does not provide all required information: This should be made impossible with the API design. 2. The user provides clearly bogus data: This must be caught and reported. Preferably with an assert. 3. The user provides plausible data. After this, the object is in a consistent state, but it does not guarantee all operations will always succeed. There are also external factors that can make an operation fail. –  Bart van Ingen Schenau Jan 13 '11 at 12:10
    
@Bart - Nice summary. I'll incorporate it into the answer. –  ChrisF Jan 13 '11 at 12:11

Achieving a consistent state can be done two ways: default values and object factories.

You can always provide a default value. In case of a connection above you just embed a small web server in you app and make http://localhost:8080 default. Same goes for other situations. You just pick a value to be default and make sure it works (even as a dummy). Sometimes it might require absurd amounts of effort so it's not the best option.

Second, a bit better approach is object factory. Given an Connection object above with few properties, you need to actually know which of the properties are crucial, and which are optional. That knowledge might be encapsulated in an object factory, which could make sure that you can either produce a consistent object or can't get object at all.

share|improve this answer

Could using a Fluent Interface help you?

Your example would then look like:

Connection c = new Connection()
    .setHost("http://whatever")
    .setPort(8080)
    .connect();

As described in the wiki article, each method of a fluent interface returns the current instance. I don't see why you couldn't return the instance cast to a specific interface based on certain variable values which in turn determines what functions are available at that point. So you could only return the instance as an interface allowing the call to connect() after both host and port have been set.

share|improve this answer

I like the style where a 'default' state of an object is a 'null', or 'invalid'. most operations in an invalid object just fail or return without side effects. You also want a 'isValid' method to check it.

it's also part of the 'never return NULL' trend, just return an invalid object of the right class. That way calling code don't have to always check for NULL results, since an invalid object is harmless, and could be 'completed' later on.

share|improve this answer
    
Which results in a situation where you can never be sure an object is valid, and you have to pepper .isValid() calls throughout your code, and come up with intelligent things to do if it's invalid. It sounds easier to test .isValid() when the object is created and do something then, and for that you may as well return NULL (or throw an exception). –  David Thornley Jan 13 '11 at 16:30
    
Yeah, that's what i thought at first; but if invalid objects are safe enough, you can just not bother to check and pass it around. it's much safer than NULL pointers, and in most cases it's not an error, just incomplete, so an exception isn't appropriate. –  Javier Jan 13 '11 at 22:33

an object is only expected to be in a consistent state in between method calls

the problem you think you have doesn't exist.

before the flames and downvotes begin, consider this: you have an object with two properties X and Y that must be equal. In the absence of true parallel assignment, there is no way to assign a new value to both properties such that the 'must be equal' constraint is not temporarily violated.

share|improve this answer

I am quite surprised noone brought it up yet.

Uninitialized or Almost initialized objects are a Bad Thing (tm), they make code unnecessarily brittle. Indeed it's always one of my major goal, during a code review, to uncover those situations who "work now" but will require extreme care to continue working each time some maintenance is performed on the code.

When an object is built, it should be usable. Any other state is heresy. It's not especially difficult either, so there is little point not doing right.

The first advice I've seen, which is a step in the right direction, is to group the host and port parameter in a single setter, and initialize them from the constructor directly. It's sound advice.

Connection conn = Connection('localhost', '8080')
conn.Connect()

You might note though, that there are still two steps to be accomplished before one can actually use the connection, and that we have an "Almost Initialized" object in the mid-term.

At this point one might rightfully object that if we read the details from some configuration file or any other mean, we might well be unable to connect at the moment we read them, and thus we just have to accept it.

I don't.

If we have two different states of things, it just mean we need two different objects:

ConnectionDescription connDesc = ConnectionDescription('localhost', '8080')

connDesc.Set('localhost', '80') // oups, the port was wrong

Connection conn = Connection(connDesc)

And low and behold: we only have perfectly consistent objects!

Note: for safety reasons, the Connection item should make a copy of the description.

share|improve this answer

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.