When designing classes you usually have to decide between:
I have seen several APIs and frameworks that use one of the above or even an inconsistent approach that differs from class to class. What are your thoughts and best practices on that subject?
The short answer is that an object should be fully initialized after its constructor was called.
This should be the default approach with the least surprises for users. There are cases when your run-time, framework or other technical constraints prevent the default approach.
In some circumstances a the Builder pattern helps to support cases when it is not possible to use a simple constructor. This approach is in the middle ground, letting users call setters to initialize and still be able to work only with fully initialized objects.
A static factory method is appropriate in cases when the object constructor needs to be more flexible than a constructor but a builder is too complex to implement.
Constructor:
x = new X(a, b);
Setter:
x = new X();
x.setA(a);
x.setB(b);
Builder:
builder = new Builder();
builder.setA(a);
builder.setB(b);
x = builder.build();
Static factory method:
x = X.newX(a, b);
All four approaches will produce an instance, x, of class X.
Constructor
Pros:
Cons:
new X(a = "a", b = "c"))Setter
Pros:
Cons:
Builder
Pros:
Cons:
Static factory method
Pros:
Cons:
Definitely "full" in all but the edgiest of edge cases.
It's very straightforward to me, that the point of a constructor is to set up the object so that it's ready to use. Obviously immutable objects are by far the best, but it's also often acceptable for some types of object to change state later. One thing to avoid, however, is having on object that can be constructed at one point, but an init() or setup() method must be called on it before use. It's irritating, confusing - and what's the point of having an object constructed if it's illegal to call "real" methods on it?
To my mind there are no substantial downsides to requiring "full" construction; the callers would have to marshall all the required arguments together before being able to use the object anyway, and making them do so in the constructor removes a whole class of errors. It's great to know that if you're passed an instance of an object it will be valid for use, rather than having some temporal and/or state dependency. If anything, this can also make it clearer to calling code exactly what dependencies need to be gathered by having them all defined in one clear place.
In fact the only concrete argument I can see for not doing this is circular dependencies; if class A requires a B and vice versa, then one or other of these relationships must be fulfilled via a setter method. Whether this represents good design is left as an exercise for the reader. :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With