Static Dependency Injection

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:

Class Diagram of the Bad Example

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:

Class Diagram of the Bad Example - Extended

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:

Final Result

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.

Effect Chain

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