Dienstag, 3. August 2010

Simple Java #4: Constructor Rules

After writing tons of java code, and experimenting a lot with the language and the different types of constructors, i will try to give some good rules of thumb for defining constructors, which I will discuss along with some information why you should obey them below.
  1. Use Constructors only to initialize fields, not to evaluate logic
  2. A Cons should initialize all or none of its fields
  3. Use only the 3 standard constructors: Default, Canonical, Copy
  4. Avoid public constructors for generic classes
  5. Don't throw recoverable (checked exceptions)
  6. Use factory methods in all other cases!

#1 Use Constructors for field initialization
This rule is actually the most important one: Do do not anything else than setting the fields. The only possible operation would be to validate input arguments against null or invalid values.
I would not even allow it to wrap input collections into immutable wrappers or something like that.
Thats either the responsibility of the caller or the factory method.

Why? Because it keeps constructors easy to understand, scale AND test.

#2 A cons should set all or none of its fields
If you design an immutable object, then set all values. For mutable ones, use the default constructor. In both cases, it will be easier for your user if he does not have to check which fields have been set and which not.
This does not take internal "cache" fields into account, which the api contract does not tell about.

#3 Use only the standard constructors
There are three common constructors which should help you for most usecases. In general, try to avoid constructor overloading to provide default values for some fields, as it is often done in the JDK. Thats better suited for factory methods.
#3.1 Default constructor.
Unlike the java api tutorials indicate, default constructors are rarely useful. In my opionion, the only possible usecases are mutable objects (which you should try to avoid) and singletons/bootstrap classes (from which you do not need more than one per isolated bundle, completely free of all logic).
A singleton default constructor should be private and mostly concerned with bootstraping. This is the only usecase i know where constructors will do more than just setting their fields: But since its private, it does not really matter at all.
#3.2. Canonical Constructor
This is usually the best choice and the obvious one for immutable objects.
#3.3  Copy Constructor
Since clone has too many problems, implementing copy constructors should usually be a common task for mutable objects. Most of the time, the copy constructor will deconstruct the values and hand them to the canonical constructor.

#4Constructors crap with generics.
This is bascially a java specification failure: On static methods, it is sufficent to mention the generic types in the declaration, for constructors, however, they need to be used on BOTH sides.
Example:
List[String] l = new ArrayList[String]();
BUT:
List[String]l = Lists.emptyList(); // return type inference.
Therefore, you should by all means use factory methods for generic types whenever possible.

Why is it like that you might ask? Because the idiots which defined generics thought that the first (constructor) notation is the one which everyone should use, but they quickly found that that it makes working with generic helper methods needlessly complicated. So they invented a (limited) typeinference for that task. Google it for details.

#5 Don't throw recoverable exceptions.
Recoverable exceptions happen if something in the "big picture" fails, esp  something concerned with the outer world, like i.o. In these cases, you should not even use factory methods: Use factory objects, which know how to handle that world AND are testable.

A classical counterexample are the java-io stream classes.

#6 Use Factories and Factory methods
All complicated cases should be handled by factory methods instead! Why?
Because:
- They can be named, so you can make your intention clear.
- They offer return type inference, making them better suited for generics
- They can choose which implementation to use <-> Do not bind to a specific implementation. Can be a huge performance advantage esp for immutables.
- Refactoring the class into an interface later is a bit easier. If java were able to define static methods on interfaces, it would be even without ANY problems at all...

Sometimes, you should even use a factory instead:
- If you have a dependency on anything complicated (like the outer world), a factory is mandatory
- For checked exceptions with recoverable situations. Static method should not contain any kind of logical branchning.
Whats up with all of this fuzz?Answer: Scaleability AND simpler testing. When you start to test, you might want to "simply" construct objects by a (package) private constructor without those crazy rules, and you might want to test the logic seperately. Both is much easier if checks and the like are not part of the constructor but of static helpers.

Also, inheritance is much easier to implement when you have at max 3 well-known constructors at hand.
And, when thinking about it, factory methods to make you intention much clearer than a plain old-meaningless constructor, from time to time.

Factory Method naming patterns
Heres a simple trick if you do not know a suitable name: Use "of" in all cases where you would have formerly created a trivial constructor.

MyClass.of(Value1, Value2) will then yield the new instance. Its short, sexy and comfortable with generics. :-)

Keine Kommentare:

Kommentar veröffentlichen