DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • The Most Popular Technologies for Java Microservices Right Now
  • Multi-Threaded Geo Web Crawler In Java
  • Step-by-Step Guide: Application Using NestJs and Angular
  • Dependency Injection Implementation in Core Java

Trending

  • How to Create a Successful API Ecosystem
  • Event-Driven Microservices: How Kafka and RabbitMQ Power Scalable Systems
  • Code Reviews: Building an AI-Powered GitHub Integration
  • Efficient API Communication With Spring WebClient
  1. DZone
  2. Data Engineering
  3. Databases
  4. Inversion of (Coupling) Control in Java

Inversion of (Coupling) Control in Java

Learn more about the inversion of control and dependency injections in Java.

By 
Daniel Sagenschneider user avatar
Daniel Sagenschneider
·
Feb. 28, 19 · Presentation
Likes (42)
Comment
Save
Tweet
Share
31.8K Views

Join the DZone community and get the full member experience.

Join For Free

What is the inversion of control? And what is dependency injection? These types of questions are often met with code examples, vague explanations, and what has been identified on StackOverflow as 'low-quality answers.'

We use inversion of control and dependency injection and often push it as the correct way to build applications. Yet, we can not clearly articulate why!

The reason is we have not clearly identified what control is. Once we understand what we are inverting, the concept of inversion of control versus dependency injection is not actually the question to be asked. It actually becomes the following:

Inversion of Control = Dependency (state) Injection + Thread Injection + Continuation (function) Injection

To explain this, well, let's do some code. And yes, the apparent problem of using code to explain inversion of control is repeating, but bear with me, the answer has always been right before your eyes.

One clear use of inversion of control/ dependency injection is the repository pattern to avoid passing around a connection. Instead of the following:

public class NoDependencyInjectionRepository implements Repository<Entity> {

  public void save(Entity entity, Connection connection) throws SQLException {
    // Use connection to save entity to database
  }
}


Dependency injection allows the repository to be re-implemented as:

public class DependencyInjectionRepository implements Repository<Entity> {

  @Inject Connection connection;

  public void save(Entity entity) throws SQLException {
    // Use injected connection to save entity to database
  }
}


Now, do you see the problem we just solved?

If you are thinking "I can now change the connection to say REST calls," and this is all flexible to change, well, you would be close.

To see if the problem has been resolved, do not look at the implementation. Instead, look at the interface. The client calling code has gone from:

repository.save(entity, connection);


to the following:

repository.save(entity);


We have removed the coupling of the client code to provide a connection on calling the method. By removing the coupling, we can substitute a different implementation of the repository (again, boring old news, but bear with me):

public class WebServiceRepository implements Repository<Entity> {

  @Inject WebClient client;

  public void save(Entity entity) {
    // Use injected web client to save entity
  }
}


With the client able to continue to call the method just the same:

repository.save(entity);


The client is unaware that the repository is now calling a microservice to save the entity rather than talking directly to a database. (Actually, the client is aware but we will come to that shortly.)

So, taking this to an abstract level regarding the method:

R method(P1 p1, P2 p2) throws E1, E2

// with dependency injection becomes

@Inject P1 p1;
@Inject P2 p2;
R method() throws E1, E2


The coupling of the client to provide arguments to the method is removed by dependency injection.

Now, do you see the four other problems of coupling?

At this point, I warn you that you will never look at the code the same again once I show you the coupling problems. This is the point in the Matrix where I ask you if you want to take the red or blue pill. There is no going back once I show you how far down the rabbit hole this problem really is — refactoring is actually not necessary and there are issues in the fundamentals of modeling logic and computer science (ok, big statement but read on — I can't put it any other way).

So, you chose the red pill.

Let's prepare you.

To identify the four extra coupling problems, let's look at the abstract method again:

@Inject P1 p1;
@Inject P2 p2;
R method() throws E1, E2

// and invoking it
try {
  R result = object.method();
} catch (E1 | E2 ex) {
  // handle exception
}


What is coupled by the client code?

  • The return type

  • The method name

  • The handling of exceptions

  • The thread provided to the method

Dependency injection allowed me to change the objects required by the method without changing the client code calling the method. However, if I want to change my implementing method by:

  • Changing its return type

  • Changing its name

  • Throwing a new exception (in the above case of swapping to a micro-service repository, throwing an HTTP exception rather than a SQL exception)

  • Using a different thread (pool) to execute the method than the thread provided by the client call

This involves "refactoring" all client code for my method. Why should the caller dictate the coupling when the implementation has the hard job of actually doing the functionality? We should actually invert the coupling so that the implementation can dictate the method signature (not the caller).

This is likely the point you look at me like Neo does in the Matrix going "huh"? Let implementations define their method signatures? But isn't the whole OO principle about overriding and implementing abstract method signature definitions? And that's just chaos because how do I call the method if it's return type, name, exceptions, arguments keep changing as the implementation evolves?

Easy. You already know the patterns. You just have not seen them used together where their sum becomes a lot more powerful than their parts.

So, let's walk through the five coupling points (return type, method name, arguments, exceptions, invoking thread) of the method and decouple them.

We have already seen dependency injection remove the argument coupling by the client, so one down.

Next, let's tackle the method name.

Method Name Decoupling

Many languages, including Java lambdas, allow or have functions as first class citizens of the language. By creating a function reference to a method, we no longer need to know the method name to invoke the method:

Runnable f1 = () -> object.method();

// Client call now decoupled from method name
f1.run()


We can even now pass different implementations of the method around dependency injection:

@Inject Runnable f1;
void clientCode() {
  f1.run(); // to invoke the injected method
}


OK, this was a bit of extra code with not much additional value. But again, bear with me. We have decoupled the method's name from the caller.

Next, let's tackle the exceptions from the method.

Method Exceptions Decoupling

By using the above technique of injecting functions, we inject functions to handle exceptions:

Runnable f1 = () -> {
  @Inject Consumer<E1> h1;
  @Inject Consumer<E2> h2;
  try {
    object.method();
  } catch (E1 e1) {
    h1.accept(e1);
  } catch (E2 e2) {
    h2.accept(e2);
  }
}

// Note: above is abstract pseudo code to identify the concept (and we will get to compiling code shortly)


Now, exceptions are no longer the client caller's problem. Injected methods now handle the exceptions decoupling the caller from having to handle exceptions.

Next, let's tackle the invoking thread.

Method's Invoking Thread Decoupling

By using an asynchronous function signature and injecting an Executor, we can decouple the thread invoking the implenting method from that provided by the caller:

Runnable f1 = () -> {
  @Inject Executor executor;
  executor.execute(() -> {
    object.method();
  });
}


By injecting the appropriate Executor, we can have the implementing method invoked by any thread pool we require. To re-use the client's invoking thread, we just use a synchronous Exectutor:

Executor synchronous = (runnable) -> runnable.run();


So now, we can decouple a thread to execute the implementing method from the calling code's thread.

But with no return value, how do we pass state (objects) between methods? Let's combine it all together with dependency injection.

Inversion of Control (Coupling)

Let's combine the above patterns together with dependency injection to get the ManagedFunction:

public interface ManagedFunction {
  void run();
}

public class ManagedFunctionImpl implements ManagedFunction {

  @Inject P1 p1;
  @Inject P2 p2;
  @Inject ManagedFunction f1; // other method implementations to invoke
  @Inject ManagedFunction f2;
  @Inject Consumer<E1> h1;
  @Inject Consumer<E2> h2;
  @Inject Executor executor;

  @Override
  public void run() {
    executor.execute(() -> {
      try {
        implementation(p1, p2, f1, f2);
      } catch (E1 e1) {
        h1.accept(e1);
      } catch (E2 e2) {
        h2.accept(e2);
    });
  }

  private void implementation(
    P1 p1, P2 p2, 
    ManagedFunction f1, ManagedFunction f2
  ) throws E1, E2 {
    // use dependency inject objects p1, p2
    // invoke other methods via f1, f2
    // allow throwing exceptions E1, E2
  }
}


OK, there's a lot going on here but it's just the patterns above combined together. The client code is now completely decoupled from the method implementation, as it just runs:

@Inject ManagedFunction function;
public void clientCode() {
  function.run();
}


The implementing method is now free to change without impacting the client calling code:

  • There is no return type from methods (slight restriction always being void, however necessary for asynchronous code)
  • The implementing method name may change, as it is wrapped by the ManagedFunction.run() 

  • Parameters are no longer required by the ManagedFunction. These are dependency-injected, allowing the implementing method to select which parameters (objects) it requires

  • Exceptions are handled by injected Consumers. The implementing method may now dictate what exceptions it throws, requiring only different Consumers injected. The client calling code is unaware that the implementing method may now be throwing an HTTPException instead of an SQLException . Furthermore, Consumers can actually be implemented by ManagedFunctions  injecting the exception.

  • The injection of the Executor allows the implementing method to dictate its thread of execution by specifying the Executor to inject. This could result in re-using the client's calling thread or have the implementation run by a separate thread or thread pool

All five coupling points of the method by its caller are now decoupled.

We have actually "Inverted Control of the Coupling." In other words, the client caller no longer dictates what the implementing method can be named, use as parameters, throw as exceptions, which thread to use, etc. Control of coupling is inverted so that the implementing method can dictate what it couples to by specifying it's a required injection.

Furthermore, as there is no coupling by the caller, there is no need to refactor code. The implementation changes and then configures in it's coupling (injection) to the rest of the system. Client calling code no longer needs to be refactored.

So, in effect, dependency injection only solved 1/5 of the method coupling problem. For something that is so successful for only solving 20 percent of the problem, it does show how much of a problem coupling of the method really is.

Implementing the above patterns would create more code than it's worth in your systems. That's why open-source OfficeFloor is the "true" inversion of control framework and has been put together to lessen the burden of this code. This has been an experiment in the above concepts to see if real systems are easier to build and maintain with "true" inversion of control. 

Summary

So, the next time you reach for the Refactor Button/Command, realize that this is brought on by the coupling of the method that has been staring us in the face everytime we write code.

And really, why do we have the method signature? It is because of the thread stack. We need to load memory onto a thread stack, and the method signature follows the behavior of the computer. However, in the real world, the modeling of behavior between objects offers no thread stack. Objects are loosely coupled with very small touch points — not the five coupling aspects imposed by the method.

Furthermore, in computing, we strive towards low coupling and high cohesion. One might possibly put forward a case that,in comparison to ManagedFunctions, methods are:

  • High coupling: methods have five aspects of coupling to the client calling code

  • Low cohesion: as the handling of exceptions and return types from methods starts blurring the responsibility of the methods over time, continuous change and shortcuts can quickly degrade the cohesiveness of the implementation of the method to start handling logic beyond its responsibility

Since we strive for low coupling and high cohesion, our most fundamental building block (the  method and the function) may actually go against our most core programming principles.

Dependency injection Inversion of control Implementation Java (programming language) Database

Published at DZone with permission of Daniel Sagenschneider. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • The Most Popular Technologies for Java Microservices Right Now
  • Multi-Threaded Geo Web Crawler In Java
  • Step-by-Step Guide: Application Using NestJs and Angular
  • Dependency Injection Implementation in Core Java

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!