SOLID software design. A look into the powerful Inversion of Control principle.

SOLID software design. A look into the powerful Inversion of Control principle.

SOLID software design. A look into the powerful Inversion of Control principle.

Photo by James Peacock on Unsplash


Introduction

The inversion of Control (IOC) design pattern lets you decouple your application from its dependencies.

In this article, we’ll discuss the concept of IOC in detail and show some examples of how to use it in your software development projects.

We’ll also look at related concepts like Dependency Injection (DI) and the Provider Pattern.

What is the Inversion of Control Principle?

The Inversion of Control principle is an essential concept in software architecture.

It states that you should build your system as a set of modular, loosely coupled, and adaptable moving parts with hinges in the right places, rather than tightly coupled code cemented with crazy glue.

One perspective of this principle is from a runtime standpoint. Suppose one part of your system becomes obsolete or fails to function correctly due to a bug or misconfiguration. In that case, only one component will be affected, instead of the entire system falling at once.

Another perspective is testability. Having swappable parts means you can mock dependencies to test isolated chunks of code.

A third view of the aim of IOC is about code base agility to foster longevity.

Codebase health was the original intent; to keep codebases supple and pliant to adapt more easily without decay.

However, today, Most discussions about IOC have fallen from the high-value, long-term, strategic rationale. Most are down in the weeds of tactical implementation, tangled up in implementation details like how particular tools or technologies work.

This is why many engineering teams stand to benefit from rediscovering the primary goal of inversion of control.

IoC Containers

An Inversion of Control container is a technology that helps you implement the Inversion of Control design pattern by providing a high-level API for managing and creating your software components.

The benefit is improved testability and flexibility because you can easily instantiate your code differently without changing any code—you only need to change the configuration file.

What is Dependency Injection? How does it help you invert control?

Dependency injection is a technique that allows you to decouple components in your application.

A dependency is something that a component needs from another component.

Inversion of control (IOC) is a way to provide dependencies for classes.

Dependency injection achieves IOC.

You can inject dependencies through constructor injection, property injection, or a method call by reference.

Dependency Injection allows you to easily change the implementation of classes at runtime without having to change any code outside its class definition or constructor signature. This results in a more testable codebase.

Construction injection vs. setter injection

Constructor Injection

Constructor Injection (CI) is a technique for injecting dependencies into your classes and objects.

You use it to create dependent objects and push them directly into the constructor, where each new class instance is created by passing in any required information about how you want to configure it.

So when you “new up” an object, you have to know the contracts required for the internal operations of the class along with some potential configuration.

Setter Injection

Setter Injection (SI) is similar to constructor injection, except that you use setters on your classes instead of constructors.

You still inject your dependencies for use later during code execution.

Setter Injection is also Dependency Injection because one type or interface can have many implementations depending on the context, and you can inject different behaviors and states.

Injections allow us flexibility when configuring our runtime dependency graph through composition rather than inheritance-based relationships between classes/interfaces.

These are the most common ways.

And they are so common that many people believe these are the ONLY ways.

But they aren’t.

There’s another powerful (but less known) way…

The third way is the provider pattern.

The third way is to use the provider pattern.

It’s a bit more sophisticated because you must know the proper technical design patterns and how to use them.

Imagine you create a utility class.

And you create a new instance of this class in the middle of your code.

You don’t inject it ahead of time. Yet it’s sitting there, available to use whenever you need it.

What’s the difference between provider instantiation and dependency injection?

Provider instantiation means you don’t have to break your encapsulation by ramming in injection points for every subcomponent.

On the other hand, there’s a simple way to look at it. At first glance, it seems that instantiation means dependencies are not inverted.

And logically, the inability to invert the dependency to what you instantiate means you require a better production code design to avoid tight coupling.

And don’t we all want low-coupled and highly testable code bases.

Now, unfortunately…

It’s where most people stop.

But imagine if we COULD get the benefits of IOC and the simplicity of instantiation without the drawbacks of injection?

To dive deeper:

  • Imagine if you somehow inverted control without dealing with the downsides of injection.
  • Or If you could avoid leaky abstractions like in constructor injection, where you have to know about the inner working of the class you are instantiating.
  • Or if you could avoid all the extra complexity from surface bloat when you punch a bunch of dirty injection points through on otherwise pristine encapsulation.

If you could get there, it would allow you to simplify your code base dramatically.

  • And it would produce more flexibility because you have even lower coupling than with injection.
  • You leak **less **surface and are better encapsulated…
  • When you keep more detail about internal operations private, it’s easier to change the inner workings without a cascading blast radius.

And, here’s the thing. It would be just as testable as the less sophisticated approaches.

If you’re reading this, you probably already know that testing is not the same as testable. Just like there’s a difference between capabilities and abilities.

If you know what you’re doing, provider instantiation is just as flexible as inversion through injection.

However, the cost to write test code around a provider pattern approach will be slightly higher, especially for less experienced coders.

Because…

  • It’s more sophisticated, meaning junior engineers may need help.
  • It’s less well-known, resulting in less tooling and support in most IOC frameworks that cater to the common denominator.
  • More infrastructure is required to support this pattern than brute-force IOC via injection.

Now, of course, it’s entirely up to you.

And to make a good choice, you now understand all the values IOC provides, including and beyond testability.

When you balance the slightly increased cost to produce test code against the lifetime of steadily rising costs from rapid code decay in your production systems (not your test code), picking the best path to come out on top should be a no-brainer.

What’s the secret?

You can get all the benefits of the provider pattern by inverting control by leveraging a factory pattern.

Inside your machinery, you take over the instantiation point of all classes you consume. You don’t have to inject them through a constructor or setter.

The provider pattern is superior because it gives you more agility by making it easier to swap out different implementations while having a composable code base.

The downside is that testing this sophisticated design requires skills. You need a more sophisticated testing approach than a simple, basic, cut-and-dry injection sledgehammer.

But, like with most things, you only get as much value as you’re willing to put in.

So you have to decide your priorities because there are tradeoffs.

Is the simplicity of your testing code base and ease of testability more critical?

Or is your priority having longevity, agility, and clarity in your production codebase driving down costs while driving up the lifespan of your products and systems?

Conclusion

The Inversion of Control principle is one of the essential concepts in software design. It lets you separate your app from its dependencies making it easier to test, adapt, and extend.

IOC Containers are frameworks for implementing the Inversion of Control principle in object-oriented languages like Java and C#.

Even though most IOC frameworks support the provider pattern, dependency injection is the most common use. Some even let your application configure itself dynamically at runtime, so you don’t have to set up any dependencies ahead of time.

The two common types of Dependency Injection have different advantages: Construction injection vs. setter injection.

And last but not least, you have the slightly more sophisticated yet surprisingly powerful provider pattern.

The downside: A provider can be more challenging to test because it’s less known, so there’s less tooling, while also requiring higher engineering skills. Your lower-skilled engineers may need help understanding how testing code interacts with a factory pattern.

The upside: With the provider approach, your code base will last longer because it is more flexible, your abstractions will be better sealed, and your implementation code will be more straightforward. This drives down software ownership costs for maintenance, complexity, and system extension.


By the way….

Are you working on a .NET code base?

Do you want a clear separation of concerns, supple code, and agile products?

Do you want to see the power of the provider pattern amplify your favorite dependency injection framework?

Check out Applinate!

It aims to give you a best-practices library and framework for microservices to keep your code base flexible and stop code rot.

Do you want to avoid figuring out, designing, and building infrastructure code and start assembling systems faster?

Applinate makes it easy to separate your infrastructure and business code.

And better encapsulation of things that change facilitates agility, adaptability, and extensibility.

It’s super flexible. Everything is adaptable, so your code base doesn’t get locked up, through explicit or conceptual coupling.

It’s not tied to any particular implementation. Instead, it’s more of a modular framework that lets you add best practices and tested patterns to your favorite tools through simple conventions.

For example, use a convention of simply implementing a specific interface to automatically integrate, allowing you to easily plug in your favorite IOC framework.

And I’m open-sourcing our framework with dozens of tools to give you a sweet, pre-baked, out-of-the-box microservice framework that’s millions of times cheaper, easier, and faster than if you were to build all this infrastructure yourself.

Click here to check out Applinate today.


RELATED POSTS


SOLID software design. A look into the powerful Inversion of Control principle.

ADDRESS

99 Wall Street #2794, New York, NY 10005

CALL US

1-800-394-2500