In such cases, it is best to use the type system of your language to help you with proper initialization. How can we prevent a FooManager
from being used without being initialized? By preventing a FooManager
from being created without the necessary information to properly initialize it. In particular, all initialization is the responsibility of the constructor. You should never let your constructor create an object in an illegal state.
But callers need to construct a FooManager
before they can initialize it, e.g. because the FooManager
is passed around as a dependency.
Don't create a FooManager
if you don't have one. What you can do instead is pass an object around that lets you retrieve a fully constructed FooManager
, but only with the initialization information. (In functional-programming speak, I'm suggesting you use partial application for the constructor.) E.g.:
ctorArgs = ...;
getFooManager = (initInfo) -> new FooManager(ctorArgs, initInfo);
...
getFooManager(myInitInfo).fooMethod();
The problem with this is that you have to supply the init info every time you access the FooManager
.
If it's necessary in your language, you can wrap the getFooManager()
operation in a factory-like or builder-like class.
I really want to do runtime checks that the initialize()
method was called, rather than using a type-system-level solution.
It is possible to find a compromise. We create a wrapper class MaybeInitializedFooManager
that has a get()
method that returns the FooManager
, but throws if the FooManager
wasn't fully initialized. This only works if the initialization is done through the wrapper, or if there is a FooManager#isInitialized()
method.
class MaybeInitializedFooManager {
private final FooManager fooManager;
public MaybeInitializedFooManager(CtorArgs ctorArgs) {
fooManager = new FooManager(ctorArgs);
}
public FooManager initialize(InitArgs initArgs) {
fooManager.initialize(initArgs);
return fooManager;
}
public FooManager get() {
if (fooManager.isInitialized()) return fooManager;
throw ...;
}
}
I don't want to change the API of my class.
In that case, you'll want to avoid the if (!initialized) throw;
conditionals in each and every method. Fortunately, there is a simple pattern to solve this.
The object you provide to users is just an empty shell that delegates all calls to an implementation object. By default, the implementation object throws an error for each method that it wasn't initialized. However, the initialize()
method replaces the implementation object with a fully-constructed object.
class FooManager {
private CtorArgs ctorArgs;
private Impl impl;
public FooManager(CtorArgs ctorArgs) {
this.ctorArgs = ctorArgs;
this.impl = new UninitializedImpl();
}
public void initialize(InitArgs initArgs) {
impl = new MainImpl(ctorArgs, initArgs);
}
public X foo() { return impl.foo(); }
public Y bar() { return impl.bar(); }
}
interface Impl {
X foo();
Y bar();
}
class UninitializedImpl implements Impl {
public X foo() { throw ...; }
public Y bar() { throw ...; }
}
class MainImpl implements Impl {
public MainImpl(CtorArgs c, InitArgs i);
public X foo() { ... }
public Y bar() { ... }
}
This extracts the main behaviour of the class into the MainImpl
.
Best Answer
Here is what the oracle javadoc guidelines say:
So summarized, repeating links to the same link target is a bad practice.
Thanks to @AaronKurtzhals for pointing me there.