I am building a wrapper for a library that requires little complex logic. The whole project is 8 builder classes, 4 classes for doing some pre-processing, and a couple visitor classes. Essentially I am just building wrapper classes around a third party library’s objects. I was curious whether in a situation like this it is more appropriate to use poor man’s di (no framework) or static class dependencies.
For static class dependencies the overhead would be when I have the same static dependency used in multiple classes, you would get some unneeded
duplication of instances.
For di, the overhead would be creating the extra builder classes to wire together the objects, in addition to a minor increase is “cognitive load” for those unfamiliar with the concept. For a library this small, without complex business logic, my gut tells me it may be overkill.
Just curious which one you guys would go with, and why. I know the difference in strategies is essentially razor thin.
Dependent Static Classes
public class DoTheThing {
private static final DependencyOne dependencyOne = new DependencyOne();
private static final DependencyTwo dependencyTwo = new DependencyTwo();
public void doSomething(Argument yayImAnArgument){
dependencyOne.do(yayImAnArgument);
dependencyTwo.do(yayImAnArgument);
}
}
vs.
Dependency Injection
public class DoTheThing {
private final DependencyOne;
private final DependencyTwo;
public DoTheThing(DependencyOne dependencyOne, DependencyTwo DependencyTwo){
this.dependencyOne = dependencyOne;
this.dependencyTwo = dependencyTwo;
}
public void doSomething(Argument yayImAnArgument){
dependencyOne.do(yayImAnArgument);
dependencyTwo.do(yayImAnArgument);
}
}
4
Your
public class DoTheThing {
private static final DependencyOne dependencyOne = new DependencyOne();
private static final DependencyTwo dependencyTwo = new DependencyTwo();
public void doSomething(Argument yayImAnArgument){
dependencyOne.do(yayImAnArgument);
dependencyTwo.do(yayImAnArgument);
}
}
is not much of an improvement over
public class DoTheThing {
public void doSomething(Argument yayImAnArgument){
new DependencyOne().do(yayImAnArgument);
new DependencyTwo().do(yayImAnArgument);
}
}
Which works just fine until the project changes. It is a Law of Demeter violation because DoTheThing
knows how to both build and use these objects.
Here’s a classic wiring pattern for pure DI:
static void main(String[] args){
// Construct and wire together all long lived objects //
DoTheThing thingDoer = new DoTheThing(
new DependencyOne(),
new DependencyTwo()
);
Argument yayImAnArgument = new SnarkyArgument("I know you are, but what am I?");
// Start the object graph ticking at one entry point //
thingDoer.doSomething(yayImAnArgument);
}
But wait! This has the same Law of Demeter violation doesn’t it? Well sure but we’ve pushed all the new
s as far up the call stack as they can go and most importantly separated them from the behavioral business logic in DoTheThing
. Which as far as I can tell is just do one before doing two.
By moving construction to main
we’ve put it somewhere safe to change. Nothing else knows about what’s happening in main so we aren’t likely to break it. All we have to do is build things here that make what we build happy.
Now sure, this is a simple example. With enough going on this construction style gets a little nutty.= That’s why creational construction patterns= exist. Because while our new
s are nicely separated in main this is brain dead procedural code. It is ok to break that up. Just keep the construction separated from the business logic. And sorry but to break it up nicely you really owe us good names.
Or just pretend the code is never going to change. Look I get it. You’ve never had to deploy anything less than the whole updated project at once. You’ve never had to change which library you were abstracting. You’ve never been told any class you touch is going to be subjected to months of artisanal integration testing done by hand. There was a time when I hadn’t either. I remember those days. Things were simpler then.
Just know, those days sneak away from you when you’re not looking.
6
What about implementing both and let the softwaredevelopper that uses your lib decide:
public class DoTheThing {
private final DependencyOne;
private final DependencyTwo;
public DoTheThing(DependencyOne dependencyOne, DependencyTwo DependencyTwo){
this.dependencyOne = dependencyOne;
this.dependencyTwo = dependencyTwo;
}
public DoTheThing() {
this(new DependencyOne(), new DependencyTwo());
}
public void doSomething(Argument yayImAnArgument){
dependencyOne.do(yayImAnArgument);
dependencyTwo.do(yayImAnArgument);
}
}