views:

310

answers:

10

I have always felt that in general the main work of a class should be done in its instance methods, while the constructor should only get the instance into a usable inital state.

But I find that in practice there are situations where it seems to make more sense to put essentially all the actual work into the constructor.

One example: I need to retrieve some DBMS-specific information from the database. The most natural way to me seemed to have a class DBMSSpecInfo, with a constructor:

public DBMSSpecInfo(java.sql.Connection conn) throws SQLException{
  // ... retrieve info from DBMS
}

/** @returns max size of table in kiB */
public int getMaxTableSize() {//...}

/** @returns max size of index in kiB */
public int getMaxIndexSize() {//...}

/** @returns name of default schema */
public String getDefaultSchema() {//...}

You would construct the class once, the constructor would fetch all data, then you could use various getters to retrieve the info you need.

Of course I could put the method somewhere else, and only use DBMSSpecInfo for the return value (essentially using DBMSSpecInfo only as a value holder), but it feels ugly to create a class just for returning values from a single function.

So what do you think? Are there problems with performing the main work in the constructor? Is it "un-idiomatic" in Java? Or is it an acceptable (though possibly uncommon) practice?

+10  A: 

The main practical problem is unit-testing - you won't be able to instantiate the object without doing actual work. (Or you'd have to mock all the classes that participate in this work).

Related talk: OO Design for testability. It gives examples of why doing work in constructors is bad for unit-testing.

Bozho
+1 very good point, missed by all other answerers (including me).
Péter Török
Good point. However, if the constructor is the *only* part that does work, the point is moot.
sleske
So I guess the lesson would be: Do your work either in the constructor or in the instance method(s), but not in both (as then you cannot test each part individually).
sleske
@sleeske - nope. Because in a unit-test you may not want to do that work _at all_. You may want to simply set its products, and use an instance of that object in other object that depend on it.
Bozho
A valid point, but why not simply provide a second constructor for use in tests?
meriton
@Bozho: True. In my case the object instance would only be used for retrieving result values, but a more complex class might be used as a parameter elsehwere. Of course, you could create an interface for the parameter type, and have a mock that implements the interface for testing...
sleske
@meriton - a special constructor only for unit-testing purposes does not sound right, although it will avoid the problem
Bozho
@meriton, that would be OK as a last resort when dealing with legacy code. But if you are just writing the code afresh, why not make it unit testable properly right from the start?
Péter Török
@Bozho: On the other hand, exposing the private internals of the actual class to setters, even for the purposes of unit testing, reeks of breaking encapsulation. Making setters for everything is no better than making the data members `public`. If you don't want to do the work, you should mock the class, not break encapsulation.
Billy ONeal
@Billy ONeal In unit-tests you don't need getters and setters. You are free to use reflection.
Bozho
@Bozho: Not all of us are using languages with reflection on a regular basis. And that's not a good idea either -- tests should *not* be testing private implementation details. (EDIT: Yes, I see the question is tagged Java, but reflection is generally glue which holds together poor designs -- that's not Java specific)
Billy ONeal
indeed - but particular unit tests are not concerned with design. It is allowed to have all sorts of 'ugliness' in there
Bozho
@Bozho: Strongly disagree on that one. If you're using reflection to test, then you're testing private implementation. If you're testing private details, that's an indication that that functionality needs to be extracted into it's own class. Your tests shouldn't break because anything behind the `private` wall changes.
Billy ONeal
@Billy ONeal - no, you are not testing private things. You are using reflection to _prepare_ the object to be tested. Watch the presentation I linked :)
Bozho
@Bozho: I did watch that presentation. Nowhere was reflection used.
Billy ONeal
Yes, but it was implied - you are setting the friendlies - i.e. the dependencies with reflection, if they are not exposed.
Bozho
A: 

In my opinion the constructor should be lightweighted and should not throw exceptions. I'd implement some kind of Load() method to retreive data from the database, or implement lazy loading.

elsni
Hm, wouldn't a `Load()` method imply two-step construction (first construct, then load()). I firmly believe two-step construction is bad (plus you can't make the class immutable).
sleske
@elsni: Could you substantiate your opinion with a foundation?
Billy ONeal
No it would not always imply a two-step construction, maybe you don't need the database access. I'd avoid expensive stuff inside the constructor because it may be not needed every time you instanciate the class.
elsni
@elsni: Yes, two-step construction would not always be necessary, but it would be *possible*, and that is enough to cause problems. And if you sometimes need a DB, and sometimes not, you should put the DB-specific stuff into a class of its own (which is just what my example is about, it's a simple class to encapsulate DB access, which is then passed to a more complex class).
sleske
+6  A: 

I would prefer separating the creation code from the class itself in such cases. It could be put into a static factory method, or a separate factory class (which can also be a public static inner class). The choice depends on the complexity of the code and the design context (which we don't know in this case).

This would also allow you to do optimizations, like caching and reusing the class instance(s).

Péter Török
Good point. Maybe I really should create a separat "value-holder" class.
sleske
+5  A: 

I'm big on pragmatism. If it works, do it! But in the name of purity and goodness, I'd like to make a design suggestion:

This class muddles up the data content with the mechanism for retrieving it. The object you end up using elsewhere is interesting only for the data it contains. So the "clean" thing to do would be to have a different class for digging out the information and then creating instances of this properties object.

That other class could have a longer lifetime, as you'd typically be calling a method to do the work, not the constructor. The constructor of DBMSSpecInfo might end up assigning a bunch of properties but not doing a lot of error-capable DB access work.

Carl Smotricz
+1 seems Péter Török and you agree :-)
sleske
+2  A: 

I don't think it is a good idea to do the main work in a constructor, since it doesn't have a return value. So it makes error processing more complicated IMO, since it forces you to use exceptions.

tcrosley
I firmly believe that, barring very unusual circumstances, error processing should *always* use exceptions. So I'd actually consider this a plus :-).
sleske
+1  A: 

A disadvantage of doing the work in the constructor is that constructors can not be overridden (nor should they delegate to overridable methods).

Another is that a constructor is all-or-nothing. If the object contains data whose initializations exhibit indepedent failures, you deprive yourself of the capability to use what data could be procured successfully. Similarly, that you have to initialize the entire object, even if you just need part of it, might adversely affect performance.

On the other hand, doing it in the constructor allows initialization state (here: the connection to the database) to be shared, and released earlier.

As always, different approaches are preferable in different circumstances.

meriton
Perhaps. OHOH it's likely that the SQL query done in the constructor in the OP's example could be done in a single SQL query, which would be an all fails or nothing fails scenario.
Billy ONeal
I agree. But for other examples, they can be a deal breaker - and I like to prevent over-generalization of design advice where I can.
meriton
+3  A: 

In your example I would make a static method GetDBMSSpecInfo(java.sql.Connection conn) that will return an instance of DBMSSpecInfo object or null if something goes wrong (in case you don't want to throw exceptions).

The DBMSSpecInfo object for me should not contain nothing more than get properties: MaxIndexSize, MaxTableSize, DefaultSchema, etc.

And I would make the constructor of this object private so that instances can only be created from the static method.

Dummy01
+1 As a matter of fact, this is just what I did. I just made the constructor of DBMSSpecInfo public, but as it's just a value containert (only `public final` fields + constructor) there's no harm in others constructing instances. Also, a public constructor helps if you need instances with predetermined values for unit testing other methods.
sleske
This is the most reasonable compromise -- doesn't destroy encapsulation but still allows relatively easy unit testing. But I still think things should just be mocked.
Billy ONeal
Yes, as you mention if you need instances with default values for unit testing is ok to have a public constructor. But then this have also to do with the functionality you want to apply. There are many cases that you want to be sure that a certain object instance was created with all the correct information. Think for example DataRow. In that case I would strongly recommend private or internal constructors.
Dummy01
+1  A: 

Doing all the work in the constructor can lead to "overload hell". You keep wanting to add more features and instead of just adding a new method, like you would in normal Object-Oriented development, you find yourself adding more and more overloaded constructors. Eventually, the constructors can grow so many overloads and parameters that it becomes unwieldy.

JoelFan
+1 I'm not yet at that point, but it's something to keep in mind.
sleske
A: 

No problem. JDK has a lot of classes that does network IO in constructors.

irreputable
Hm, the fact that the JDK classes do it does not mean it's a good practice. Some would even argue it's just the opposite... :-/
sleske
+1  A: 

Just be careful that the object is not cloned/deserialised. Instances created this way do not use the constructor.

Steven
Well, I've never serialized an object, and I believe `clone` is *evil*. Still, good to keep this in mind...
sleske