In this article I will show you a pattern I call static dependency injection. This can be used to automatically include new functionality to a module by just creating new classes - not modifying any line of existing code.
Static dependency injection is a pattern to be used when you know you’ll have lots of things to be processed in a same spot. With it, you can create classes conforming to the Single Responsibility Principle (SRP), Open/Closed Principle (OCP) and Dependency Inversion Principle (DIP). If you don’t know about SOLID principles, I recommend taking a look at this previous article.
Sometimes when discussing this pattern, I hear about Spring Framework (most of the time using annotation based injection). My knowledge of the framework itself is very basic, but I can say static dependency injection solves another design issue:
- In dependency injection, you need to inject an instance of an object you don’t know at compile time (or you want to depend only on the abstraction, not the realization).
- In static dependency injection you have a bunch of behaviors implemented in various classes. Instead of selecting one of these behaviors to inject, you want them all to be executed/available.
Using Spring can make static dependency injection easier. As the goal is not to discuss Spring and for simplicity, I’ll stop mentioning it now.
Let’s see an example on how you can use static dependency injection, and the differences to plain dependency injection may be clearer at the end.
A Bad Example
Consider you have a class to generate a report. Depending on some rules, this report may have some attachments as shown in the code below.
public class ReportGenerator() {
public Report generateReport() {
Report report = new Report();
// code to fill the report
if (report.getParameterA() < 10) {
report.addAttachment(processXAttachment());
} else if (report.getParameterB() == 50) {
report.addAttachment(processYAttachment());
}
if (report.getParameterB() == 99) {
report.addAttachment(processZAttachment());
}
}
// other methods' implementations
// are unnecessary for understanding
// ...
}
Here’s a class diagram:
I understand the reasons it may seem OK at first sight. We have “uncoupled” code (everything report is processed at its own method). Just encapsulating code in methods does not provide uncoupling. Also, the rules to process each attachment are confusing.
We could try to rewrite this way:
public class ReportGenerator() {
public Report generateReport() {
Report report = new Report();
// code to fill the report
if (shouldAttachX()) {
report.addAttachment(processXAttachment());
}
if (shouldAttachY()) {
report.addAttachment(processYAttachment());
}
if (shouldAttachZ()) {
report.addAttachment(processZAttachment());
}
}
// other methods' implementations
// are unnecessary for understanding
// ...
}
Flaws of the Design
If you believe SOLID principles are the way to go, you may have spotted the flaws in this design. Let’s evaluate it against SRP, OCP and DIP.
Single Responsibility Principle (SRP)
SRP tells us:
A module should have one, and only one, reason to change.
Consider this report is generated for a manager with information of a job applicant. Attachment X is Human Resource’s information about the applicant, while Attachment Y is the result of a technical test.
The development organization and HR may have completely different reasons for each attachment to be modified. So having the code to process these different attachments in the same class is an SRP violation.
Open/Closed Principle (OCP)
OCP is clear:
A software artifact should be open for extension but closed for modification.
We have this report considering Attachments X, Y, and Z.
What happens if now we need to include
Attachments I, J, and K?
We’ll modify the class creating more methods to process these
attachments. We’ll also need to modify the generateReport
method.
OCP violation.
Looking at the diagram below we’ll see how this class will grow when adding new attachments to it:
Dependency Inversion Principle (DIP)
My favorite principle states that you should:
Depend on abstractions, not concretions.
Clearly we depend on concretions. We call
processXAttachment
, processYAttachment
, and
processZAttachment
instead of doing so via an
abstraction.
How to Fix It with Static Dependency Injection
Let’s see how to conform to SOLID Principles.
Invert Dependencies
First, we’ll need to invert dependencies. It shouldn’t matter which is the attachment we are processing at the report. They all must realize an abstraction. The report does not need to know about these realizations (or concretions).
public interface ReportAttachment {
public AttachmentFile process() throws
AttachmentProcessingError,
NoAttachmentProcessed;
}
Every attachment class must only implement the method process
which will return an instance of AttachmentFile
(another
abstraction - it can be a text file, a PDF etc.). The rules
for processing the report will be at the implementations of this
interface. The exception NoAttachmentProcessed
tells the attachment
wasn’t processed, and it is not an error (I believe it is far
better than returning null). `AttachmentProcessingError, as the
name tells us, is an error.
Implement Classes With Single Responsibility
In our case, we want to have different classes for each attachment.
public class AttachmentZ implements ReportAttachment {
private Report report;
public AttachmentZ(Report report) {
this.report = report();
}
public AttachmentFile process() throws
AttachmentProcessingError,
NoAttachmentProcessed {
if(!shouldProcess()) {
throw new NoAttachmentProcessed();
}
AttachmentFie file;
// code to process the attachment
return file;
}
private boolean shouldProcess() {
// check what is necessary
}
}
The same for Attachments X, and Y.
Call All Dependencies
Now that we have inverted the dependency, our report does not depend on the concretions of Attachments X, Y, and Z. Even separating these attachments in other classes, we may incorrectly continue calling them directly like:
public Report generateReport() {
Report report = new Report();
// create the report;
// suppressing the exceptions for clarity of the example
report.addAttachment(new AttachmentX(report).process());
report.addAttachment(new AttachmentY(report).process());
report.addAttachment(new AttachmentZ(report).process());
}
Then our code will be open to modification: a
new attachment will modify generateReport
method.
Also, the code does not depend on abstractions. It calls directly
AttachmentX
, AttachmentY
, and AttachmentZ
. Even if we
have some kind of Factory to instantiate the attachments:
knowing about the concrete classes there can cause
OCP violation.
This is when static dependency injection shows its value: the code needs to rely on the abstraction to gather all concretions. Consider the code below:
public class ReportGenerator() {
public Report generateReport() {
Report report = new Report();
for (ReportAttachment attachment : getPossibleAttachments()) {
// suppressing exceptions for clarity sake
report.addAttachment(process());
}
}
private Collection<ReportAttachment> getPossibleAttachments() {
// return all implementations of ReportAttachment interface
//
// no information other than the interface should be
// necessary to do so
}
// other methods' implementations
// are unnecessary for understanding
// ...
}
See that ReportGenerator will traverse through all implementations
of ReportAttachment without knowing about them. Nice
considering OCP: if we need to include new attachment, only creating
a new class is necessary. It will be automatically called without
modifying ReportGenerator
.
How to Get the Concretions
To getPossibleAttachments()
you can, for example, use
Reflection
or an indexing library like classindex (see the
example below for code on how to use classindex).
In other languages:
- Python: If a class is subclassed from
object
, you can call the__subclasses__
method. - ABAP: read from
TADIR
- a database table that stores information about objects.
Final Result
See how the classes relate to each other now:
As we already saw, only creating a new implementation of ReportAttachment
is necessary
to add functionality. Without changing any other line of code. SRP, OCP and DIP are
followed.
Other Example
If you want to put your hands on code, try the repository https://gitlab.com/jwaghetti/effectchain.
Here you can create a chain of audio effects. Just type a lowercase name
of an effect, and it will be added to the chain. Typing end
will terminate
the program, printing the chain you created as below:
Enter effect name (lowercase) or 'end' to terminate the chain:
overdrive
Enter effect name (lowercase) or 'end' to terminate the chain:
distortion
Enter effect name (lowercase) or 'end' to terminate the chain:
flanger
Enter effect name (lowercase) or 'end' to terminate the chain:
envelopefilter
Enter effect name (lowercase) or 'end' to terminate the chain:
reverb
Enter effect name (lowercase) or 'end' to terminate the chain:
delay
Enter effect name (lowercase) or 'end' to terminate the chain:
inexistenteffect
Effect unavailable
Enter effect name (lowercase) or 'end' to terminate the chain:
end
-------- Effect Chain:
Input -> Overdrive -> Distortion -> Flanger -> Envelope Filter -> Reverb -> Delay -> Output
Effect classes (Delay
, Overdrive
etc.) implement the Effect
interface.
EffectFactory
automatically knows all the implementations of
the Effect
interface to instantiate them.
Adding a new effect that can be instantiated by EffectFactory
consists
simply in creating a new class implementing Effect
.
Let’s take a look in the code for the commands of the command line interface:
private static void processCommand(String command) {
if ("end".equals(command)) {
printChain();
System.exit(0);
} else {
addEffect(command);
}
}
Can you see the difference in effort (today and in future)
to create a new command compared to creating a new effect?
This code is hard to extend, it can get messy as new commands
are created, and it will become harder to test and maintain.
Of course command processing can be implemented
using a better pattern (even static dependency injection). Creating
technical debts like this must be an informed decision. I decided to leave
like this because the example for static dependency injection
is already in the Effect
implementations.
Comment if you liked the static dependency injection pattern. Do you know any pattern similar to this? I would love to hear you if you have any suggestion to improve it. Share with us code you have used this pattern, so the discussion is enriched with more examples!
Changelog
- 2020-01-09: modification on how implementations of classes are retrieved in https://gitlab.com/jwaghetti/effectchain