Write a so called factory class which builds your whole project in one step.
Example:
// a class which store configuration parameters - must be instanstiable!
public class Configuration {
public Configuration() {
// get configuration from Properties file, Resource Bundle etc.
}
}
public class A {
private Configuration configuration;
public A(Configuration configuration) {
this.configuration = configuration;
}
}
public class B {
private Configuration configuration;
private A a;
public B(A a, Configuration configuration) {
this.a = a;
this.configuration = configuration;
}
}
public class C {
private Configuration configuration;
private B b;
public C(B b, Configuration configuration) {
this.b = b;
this.configuration = configuration;
}
}
Here you have 3 classes and one configuration class. All of them are dependent on the configuration class, C is dependent on B and B is dependent on A.
As you can see, the dependencies are reflected by the constructor parameters, which is good because dependencies are explicit (that means, you now which dependencies are needed without having too look at the source code).
But, how do you build this object graph? Well, by using a factory class (here, it's even a static factory):
public class ApplicationFactory {
// prevents instantiation
private ApplicationFactory() {};
public static C buildApplicationGraph() {
// first, build the leaf objects (objects without other dependencies), here
// Configuration
Configuration configuration = new Configuration();
// now, start injecting the dependencies needed
// a only need a Configuration object
A a = new A(configuration);
// B needs a Configuration and an A object
B b = new B(a, configuration);
// we're done here
return new C(b, configuration);
}
}
As you can see, you are building the object graph bottom up. All dependencies are explicit, and you are seperating the construction process from the business logic.
What we have done here is constructor dependency injection, i.e. we passed in the dependencies every class needs via the constructor. And for creating the objects needed, we wrote a factory.
In the end, we have lightweight classes (no construction work here), explicit dependencies (which you don't have using a Singleton), and maximum flexibility (the factory could even return a subclass of C).
EDIT
One further advantage is that you're able to test you classes in isolation, as you could easily mock the parameters (e.g. by passing in subclasses of the parameters).