Java Design Patterns – Legitimate Real Work in a Constructor?

constructorsdependency-injectiondesigndesign-patternsjava

I am working on a design, but keep hitting a roadblock. I have a particular class (ModelDef) that is essentially the owner of a complex node tree built by parsing an XML schema (think DOM). I want to follow good design principles (SOLID), and ensure that the resulting system is easily testable. I have every intention of using DI to pass dependencies into the constructor of ModelDef (so that these can easily be swapped out, if need be, during testing).

What I'm struggling with, though, is the creation of the node tree. This tree is going to be made up entirely of simple "value" objects which will not need to be independently tested. (However, I may still pass an Abstract Factory into ModelDef to assist with the creation of these objects.)

But I keep reading that a constructor should not do any real work (e.g. Flaw: Constructor does Real Work). This makes perfect sense to me if "real work" means constructing heavy-weigh dependent objects that one might later want to stub out for testing. (Those should be passed in via DI.)

But what about light-weight value objects such as this node tree? The tree has to be created somewhere, right? Why not via the constructor of ModelDef (using, say, a buildNodeTree() method)?

I don't really want to create the node tree outside of ModelDef and then pass it in (via constructor DI), because creating the node tree by parsing the schema requires a significant amount of complex code — code that needs to be thoroughly tested. I don't want to relegate it to "glue" code (which should be relatively trivial, and will likely not be directly tested).

I have thought of putting the code to create the node tree in a separate "builder" object, but hesitate to call it a "builder", because it doesn't really match the Builder Pattern (which seem to be more concerned with eliminating telescoping constructors). But even if I called it something different (e.g. NodeTreeConstructor), it still feels like a bit of a hack just to avoid having the ModelDef constructor build the node tree. It has to be built somewhere; why not in the object that's going to own it?

Best Answer

And, besides what Ross Patterson suggested, consider this position which is the exact opposite:

  1. Take maxims such as "Thou Shalt Not Do Any Real Work In Thy Constructors" with a grain of salt.

  2. A constructor is, really, nothing but a static method. So, structurally, there is really not much difference between:

    a) a simple constructor and a bunch of complex static factory methods, and

    b) a simple constructor and a bunch of more complex constructors.

A considerable part of the negative sentiment towards doing any real work in constructors comes from a certain period of the history of C++ when there was debate as to precisely what state the object will be left in if an exception is thrown within the constructor, and whether the destructor should be invoked in such an event. That part of the history of C++ is over, and the issue has been settled, while in languages like Java there never was any issue of this kind to begin with.

My opinion is that if you simply avoid using new in the constructor, (as your intention to employ Dependency Injection indicates,) you should be fine. I laugh at statements like "conditional or looping logic in a constructor is a warning sign of a flaw".

Besides all that, personally, I would take the XML parsing logic out of the constructor, not because it is evil to have complex logic in a constructor, but because it is good to follow the "separation of concerns" principle. So, I would move the XML parsing logic into some separate class altogether, not into some static methods that belong to your ModelDef class.

Amendment

I suppose that if you have a method outside of ModelDef which creates a ModelDef from XML, you will need to instantiate some dynamic temporary tree data structure, populate it by parsing your XML, and then create your new ModelDef passing that structure as a constructor parameter. So, that could perhaps be thought of as an application of the "Builder" pattern. There is a very close analogy between what you want to do and the String & StringBuilder pair. However, I have found this Q&A which seems to disagree, for reasons which are not clear to me: Stackoverflow - StringBuilder and Builder Pattern. So, to avoid a lengthy debate over here as to whether the StringBuilder does or does not implement the "builder" pattern, I would say feel free to be inspired by how StrungBuilder works in coming up with a solution that suits your needs, and postpone calling it an application of the "Builder" pattern until that little detail has been settled.

See this brand new question: Programmers SE: Is “StringBuilder” an application of the Builder Design Pattern?