Injecting a Dependency
Interpreters, Iteration 12
If you want to impress an employer, learn all you can about dependency injection. Those who practice it will be very impressed and hire you on the spot; those who don't practice it will be impressed anyway. (Note: you don't need to read the Wikipedia article yet; the job offer is not guaranteed; and you don't want to work for those you don't practice it.)
"Hard to Test" or "Too Much Responsibility"
Or both.
The HobbesInterpreter
will become harder and harder
to test as you add more operator algorithms. Presently, you're
adding another assertion in HobbesInterpreterTest
as
well as a new test-case class (like
AdditionAlgorithmTest
,
MultiplicationAlgorithmTest
, etc.). This leads to a
lot of testing redundancy.
The underlying reason why it's becoming hard to test a
HobbesInterpreter
is that it's taking too much
responsibility for the map of operator algorithms. Its official job
is to lookup and apply one of those algorithms, but should it be
responsible for creating the contents of the map?
No, it shouldn't.
What if instead of creating the map in the
HobbesInterpreter
constructor you passed it in as an
argument?
Let's start with a sanity check.
It's okay if you've named the instance variable something else as long as it's descriptive of its purpose.
HobbesInterpreter
should have exactly one
constructor. It should initialize an instance variable (which I
called myOperators
) as a new hash-map of
Operator
s mapped to OperatorAlgorithm
s.
Then it adds four operator algorithms.
If your code is different, refactor it into this state. Green bars all around.
Moving Code out of the Constructor
The allocation of the hash map and the putting of operator algorithms needs to move out of the constructor. The trouble is that the constructor does the allocation and putting directly on its instance variable. To extract a method, the constructor should do this work on a local variable and assign it to the instance variable at the very end.
Leaving the myOperators
instance variable alone,
turn the use of myOperators
in the constructor into a
local variable. (This is just a matter of putting a data type in
front of its allocation.)
At the end of the constructor, use this.myOperators =
myOperators;
to initialize the instance variable.
Green bars all around.
Rename the local variable in the constructor (to something like
operators
). You can then turn
this.myOperators
into just
myOperators
.
Green bars all around.
Now Eclipse (or yourself, by hand) can extract a method.
Select the allocation and putting code in the
HobbesInterpreter
constructor. Use Refactor
-> Extract Method to extract a new method (named
something like createOperatorMap
).
Green bars all around.
Not the Constructor's Responsibility
The constructor is still doing too much work: it's calling
createOperatorMap()
itself. (Yes, that's too much
work!)
Make createOperatorMap()
public
and
static
.
Green bars all around.
This will make it easier (and possible) to call when necessary.
Delete the operators
local variable in the
constructor, and create it as a parameter of the constructor
instead.
Yellow alert!
The compiler should complain. Before you look at the complaints, can you predict where the errors will be and what the complaints are?
For each compiler error, replace new
HobbesInterpreter()
with new
HobbesInterpreter(HobbesInterpreter.createOperatorMap())
.
Green bars all around!
How accurate were you predictions about the errors? Can you explain why you were wrong (if you were)?
Still Too Much
Some might argue that createOperatorMap()
could
stay in HobbesInterpreter
since it'll be the default
operator map anyway. Others might just want to prove that
HobbesInterpreter
doesn't have to worry about creating
that operator map at all.
Creating objects for other objects to use is considered the job of a factory.
- Create a new class named
HobbesInterpreterFactory
. - Use Refactor -> Move... to move
createOperatorMap()
toHobbesInterpreterFactory
. (Eclipse will automatically move the method and update the places where you call the method.)
Green bars all around.
Observe how HobbesInterpreter
knows absolutely
nothing about ADD
, MULTIPLY
, or
any other specific Operator
!
Definitions and the Future
Incidentally, adding operator
as a parameter to the
HobbesInterpreter
constructor is a refactoring that
Eclipse could help you with: "Change Method Signature". Feel free
to use that in the future, but it glosses over a lot of details I
wanted you to see.
The more important term for this iteration is
"dependency injection". A
HobbesInterpreter
has a dependency on a map of
operator algorithms. If you hard code the dependency in the class
(i.e., in the constructor), you've locked yourself into that
one dependency.
By requiring that the map be passed in as a argument to the constructor, anyone can "inject" any map that they want. This is some incredible flexibility without much work!
One good test to see if you're doing good dependency injection is to look at your constructors. They should receive parameters and set instance variables. They should not do any computations at all.
There are other benefits. HobbesInterpreter
has
fewer responsibilities now. Smaller is almost always better, and
you've broken two responsibilities into two different places.
And as I promised from the very beginning, this change will make
testing a HobbesInterpreter
easier. However, you
should be very skeptical about this since it's not easier
(yet), and this iteration is obviously coming to a close. While the
testing will be easier, there's a new library to set up
and learn—enough work to warrant a separate iteration!