Caizin logoCaizin logoCaizin logoCaizin logo
  • Building the RIGHT product
  • Building the PRODUCT right
  • Work Portfolio
  • Resources
    • Blogs
    • Case Studies
    • White Papers
  • Contact Us
✕
Maximizing ROI: The Value of User Experience (UX) Design

11: Dependency Inversion Principle vs Inversion of Control vs Dependency Injection

Published by Avinash Wable on August 15, 2023
Categories
  • caizin capabilities
  • Outsourced Product Development
  • Programming
  • saas product development
Tags
  • delivering excellence
  • Foundational guide
  • Principles
  • Programming
  • software development

In the field of Object-Oriented Software Development, the SOLID principles stand as foundational guides, driving software systems toward modularity, loose coupling, maintainability, and robustness. The last of these principles, Dependency Inversion Principle (DIP), seems to be often mixed up with other popular concepts such as Inversion of Control and Dependency Injection.

While learning a new programming language, we often begin with a single class and method, focusing on mastering basic language features while learning data structures and algorithms. We get acquainted with Object-Oriented Programming (OOP) concepts like classes, objects, and inheritance at a basic level (e.g., Car is a Vehicle, Manager is an Employee, etc.). I have also observed while interviewing candidates that not only the freshers but even the experienced developers lack a deeper understanding of the nuances of Dependency Inversion Principle (DIP), Inversion of Control (IOC), and Dependency Injection (DI).

With this blog post, I have tried to present the intricate distinctions between these concepts, unraveling their nuances in simple terms so that even novice learners of Object-Oriented Programming will understand quickly.

The definition of the Dependency Inversion Principle tells us…

  1. High-level modules should not depend on (import anything from) low-level modules. Both should depend on abstractions (e.g., interfaces).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend upon abstractions.

To understand how DIP, DI, and IOC differ and relate to each other, let’s take an example of calculating foreign exchange.

Journey to Dependency Inversion (Principle)

  1. We start with a simple code.
class ForexService {
	public double getValue(double amount, String fromCurrency, String toCurrency) {
		if ("USD".equals(fromCurrency) && "INR".equals(toCurrency)) {
			return amount * 82.0;
		} else {
			//....
		}
	}
}

Nobody writes such code these days.

  1. So, the first step towards introducing a dependency is to move out the logic of getting an exchange rate into a separate method. The caller method getValue depends on the called method getExchangeRate (dependency).
class ForexService {
	public double getValue(double amount, String fromCurrency, String toCurrency) {
		return amount * getExchangeRate(fromCurrency, toCurrency);
	}

	private double getExchangeRate(String fromCurrency, String toCurrency) {
		if ("USD".equals(fromCurrency) && "INR".equals(toCurrency)) {
			return amount * 82.0;
		} else {
			//....
		}
	}
}

Nobody writes such code anymore, but fresh learners can relate to this.

2. The next step is to create a separate class to handle all logic related to determining an exchange rate.
In this example the ForexService is a high-level class/module that uses a low-level class/module ExchangeRateService.

class ForexService {
	private ExchangeRateService service = new ExchangeRateService();

	public double getValue(double amount, String fromCurrency, String toCurrency) {
		return amount * service.getExchangeRate(fromCurrency, toCurrency);
	}
}

class ExchangeRateService {
	public double getExchangeRate(String fromCurrency, String toCurrency) {
		if ("USD".equals(fromCurrency) && "INR".equals(toCurrency)) {
		return 82.0;
	} else {
		//....
	}
}

But now, there is a tight coupling between ForexService and ExchangeRateService. e.g., If we want to unit test ForexService, we must also provide ExchangeRateService.

3. So, the next step is to introduce an abstraction. The service provider creates an interface, and the high-level module ForexService now uses that interface.

interface ExchangeRateServiceInterface {
	double getExchangeRate(String currency);
}

public class ExchangeRateServiceImpl implements ExchangeRateServiceInterface {
	public double getExchangeRate(String fromCurrency, String toCurrency) {
		//
	}
}

// --------- imports libraries containing above code
class ForexService {
	private ExchangeRateServiceInterface service = new ExchangeRateServiceImpl();

	public double getValue(double amount, String fromCurrency, String toCurrency) {
		return amount * service.getExchangeRate(fromCurrency, toCurrency);
	}
}

We may now provide a mock implementation for unit testing.
However, in practice, exchange rate service won’t be that simple and must evolve over time. It might be reading exchange rates from a file, but now it needs to fetch it from a third-party service that provides live exchange rates. Or there might be multiple providers. I leave it to your imagination how complex it might become; the important point to understand here is – our high-level module ForexService still depends on the low-level module ExchangeRateServiceImpl.

How? The interface ExchangeRateServiceInterface is provided/owned by ExchangeRateServiceImpl. If ExchangeRateServiceImpl changes in a way that requires ExchangeRateServiceInterface to change, then our high-level module ForexService would also need to change.
Those who have difficulty understanding what is that way that might force the interface to change think of a need to change the getExchangeRate method to accept another parameter (whatever might be the reason); Because the implementation owns the interface, they are free to change it any way they like (kind of). And if this happens, ForexService needs to change its code accordingly.

4. So, the next step is to invert the dependency. Make the abstraction/interface owned by a high-level module (or make it public and controlled by high-level module(s)). Implementors/Service Providers will follow the contract introduced by the abstraction. This way, we have achieved both the requirements of DIP…

  • The high-level module ForexService as well as low-level module ExchangeRateServiceImpl both depend on the abstraction ExchangeRateServiceInterface.
  • Abstraction ExchangeRateServiceInterface doesn’t depend on the implementation details ExchangeRateServiceImpl. Implementation depends on the abstraction here.

We can think of Standards and Protocols. Communities, Committees working on standards and protocols define common APIs/contracts as interfaces; users write their code using these APIs; providers write implementations; users inject these implementations when they run their code.
e.g. JPA defines ORM APIs – Hibernate gives implementation; Java Collections API defines API for different collections – Java Collections implementation classes or Apache commons-collections gives implementations; Servlet Specifications give us Servlet APIs – Tomcat server gives implementation.

In our example, ForexService can now change the actual implementation by instantiating the new ExchangeRateServiceInterface implementation as and when required.

public class LiveExchangeRateService implements ExchangeRateServiceInterface {
	public double getExchangeRate(String fromCurrency, String toCurrency) {
		// fetches from third party service
	}
}

class ForexService {
	private ExchangeRateServiceInterface service = new LiveExchangeRateService();

	public double getValue(double amount, String fromCurrency, String toCurrency) {
		return amount * service.getExchangeRate(fromCurrency, toCurrency);
	}
}

Both the requirements of DIP have been fulfilled; can we say we have implemented DIP? Can we further improve the overall design?

Yes, you noticed it correctly! The ForexService, our high-level module, is still creating instances of low-level modules – ExchangeRateServiceImpl/LiveExchangeRateService/MockExchangeRateService etc.

It does not depend on those low-level modules, but it still needs to initialize them. In other words, our high-level module is still creating and initializing low-level modules. So, we need to get rid of this to achieve DIP fully.

Inversion Of Control

Why is it important?

We could say who else than the user (ForexService) can better tell what it needs. You are absolutely right! But, from the user’s side, why should the user be responsible for initializing those dependencies? Why not make those available to the user, properly initialized, and ready to use? Also, think of complex examples where the number of dependencies is large and/or initializing dependencies is itself a complex process.

Here comes the Inversion of Control and Dependency Injection. We need to invert the control of creation and initialization of dependencies to someone other than these two parties (user and dependency). Once the dependencies have been initialized, we can make those available (inject) to the user.

What is IOC?

To better understand IOC, the Command line vs. User Interface application is the best example. Consider a simple login flow to any application where a username and password are used for authentication.

The Command line app prompts for a username, we enter the username, then it prompts for a password, we enter the password, then it logs us in. The application is controlling the flow OR the application developer has control over the flow of the events.

On the other hand, the UI app presents us with UI elements where we can enter username and password any time, in any order. UI application code registers event handlers with the “controller” controlling the flow. This controller (mostly browser) decides to call the handler registered to an event or what thing to do next.

IOC introduces a “controller” that controls the flow on similar lines. We call it a framework or container as it acts as a container for various instances it creates (e.g., Spring, Angular frameworks). The framework creates dependencies and injects them into the users of those dependencies.

Another way to understand IOC is to think of a web application. In the case of a web application, the web server is in control of the flow of the events. Web application developer codes HTTP request handlers as dependencies of the web server. The web server reads the configuration where we map the request to its handler. The web server then initializes these dependencies and invokes handlers when a respective URL is requested.

As an example, below code shows how the controller/container/framework might be creating an instance.

class Container {
	ExchangeRateServiceInterface createExchangeRateService(String type) {
		switch (type) {
			case "SIMPLE": return new ExchangeRateServiceImpl();
			case "LIVE": return new LiveExchangeRateService();
			case "MOCK": return new MockExchangeRateService();
			default: throw IllegalArgumentException("unknown type");
		}
	}
}

This is the most fundamental illustration of creating instances. How the framework creates and initializes different instances varies by the framework. Spring, for example, provides ways to declare classes using annotations or configuration files so the framework can identify and initialize those. You can refer to the information available online about how IOC works and is implemented in various frameworks.

Dependency Injection

Once the IOC container creates the instance of a dependency, the next step is to make it available to the user of that dependency. IOC abstracts out the object creation logic. Dependency Injection gives us different ways to inject that object into other objects wherever it is required.

The below example shows constructor injection. The user (ForexService) defines a constructor accepting an abstraction of the dependency (ExchangeRateServiceInterface). Then the controller/ container/framework would initialize the User (ForexService) with the appropriate instance of the dependency (e.g., ExchangeRateServiceImpl) it has already created.

class ForexService {
	private ExchangeRateServiceInterface service;
	
	public ForexService(ExchangeRateServiceInterface service) {
		this.service = service;
	}

	public double getValue(double amount, String fromCurrency, String toCurrency) {
		return amount * service.getExchangeRate(fromCurrency, toCurrency);
	}
}

In the case of Setter Injection, a user (ForexService) defines a setter method accepting an abstraction of the dependency. The controller/framework would create an appropriate instance of the service provider and call the setter method of ForexService with that provider.

class ForexService {
	private ExchangeRateServiceInterface service;
	
	public void setForexService(ExchangeRateServiceInterface service) {
		this.service = service;
	}

	public double getValue(double amount, String fromCurrency, String toCurrency) {
		return amount * service.getExchangeRate(fromCurrency, toCurrency);
	}
}

Interface Injection is similar to the setter injection. The only additional thing is that the dependency user is required to implement an interface that has the definition of the setter method. So, the setter implementation is made mandatory for the user of the dependency.

interface ExchangeRateServiceUser {
    public void setExchangeRateService(ExchangeRateServiceInterface service);
}

class ForexService implements ExchangeRateServiceUser {
	private ExchangeRateServiceInterface service;
	
	public void setExchangeRateService(ExchangeRateServiceInterface service) {
		this.service = service;
	}

	public double getValue(double amount, String fromCurrency, String toCurrency) {
		return amount * service.getExchangeRate(fromCurrency, toCurrency);
	}
}

Below is an example of how the container might be injecting dependencies.


class Container {
    ExchangeRateServiceInterface exchangeRateService;
    ForexService forexService1, forexService2;
    
	public void initialize(/*params*/) {
		this.exchangeRateService = createExchangeRateService("SIMPLE"); // implementated above
		forexService1 = new ForexService(this.exchangeRateService); // constructor injection
		forexService2 = new ForexService();
		forexService2.setExchangeRateService(exchangeRateService); // setter injection
	}
}

Again, this is the crudest example of how dependency injection is done. How it is actually implemented varies from framework to framework. e.g., Spring framework supports setter as well as constructor injection. Also, the @Atowired annotation doesn’t require us to define a constructor or setter method these days. You can refer to the online documentation of the framework for more information.

Conclusion: 

So, can we spot the differences?
DIP is a principle that focuses on high-level and low-level modules being independent of each other and establishing a contract between them using abstractions/interfaces. It’s then left to you to decide how to create and inject dependencies.
IOC is a mechanism to achieve inversion of control of object creation and initialization by handing it over to the third party (framework). Whereas,
DI is a technique to make these dependencies available to the user of those dependencies.

Also, observe the elegance of their collaboration to achieve modularity, loose coupling, maintainability, and robustness. They serve as the foundation for the development of many frameworks like Spring, Angular etc.

References and further reading…

Inversion of Control Containers and the Dependency Injection pattern

bliki: InversionOfControl

Dependency inversion principle

Lost in Translation: Dependency Inversion Principle, Inversion of Control, Dependency Injection & Service Locator – Applied Information Sciences

 The IoC container

Print Friendly, PDF & Email
Share
60
Avinash Wable
Avinash Wable
Avinash loves coding, and the challenge of writing easy (to understand) code makes him happier when he constantly refactors it. He has 18+ years of experience in developing full-stack distributed web applications using Java and related technologies. Beyond coding, Avinash enjoys exploring the world through travel and trekking and is also passionate about farming. Deeply interested in ecological and sustainable development, Avinash is actively involved in water and soil conservation efforts in his village. A relatively new-found interest is consuming satirical content and reading about history.

Related posts

August 2, 2023

10: Maximizing ROI: The Value of User Experience (UX) Design


Read more
July 20, 2023

9: The Fall of Product Roadmaps


Read more
April 27, 2023

5: Embracing the Power of the Technology Radar for Caizin


Read more

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Agile Project Management
  • Awards & Certifications
  • caizin capabilities
  • Company Culture
  • Customer Loyalty
  • Featured Posts
  • Outsourced Product Development
  • Outsourced Product Development
  • Press Release
  • product manager
  • Programming
  • saas product development
  • Scrum Master
  • Tech Radar
  • technology trends
  • Thought Leadership
  • UX Design

Blog Authors

avatar for adminadmin

I am a passionate coder and a blogger with
almost 10 years of experience in IT industry.

Anand ShuklaAnand Shukla

Anand is an accomplished Sales Leader with over 15 years of extensive work experience in the IT Industry across the US, Europe, and APAC markets. He has in-depth knowledge of software development and IT Services for various industry verticals. Anand has contributed significantly to the startup ecosystem, helping businesses build and scale products and advising fast-paced startups on their growth strategy.

Anand is an intrepid traveler—he has visited more than 38 countries—loves riding motorcycles, having interesting conversations with people, and is always up for an adventurous ride!

Avinash WableAvinash Wable

Avinash loves coding, and the challenge of writing easy (to understand) code makes him happier when he constantly refactors it. He has 18+ years of experience in developing full-stack distributed web applications using Java and related technologies.

Beyond coding, Avinash enjoys exploring the world through travel and trekking and is also passionate about farming. Deeply interested in ecological and sustainable development, Avinash is actively involved in water and soil conservation efforts in his village.
A relatively new-found interest is consuming satirical content and reading about history.

Hemant BhattHemant Bhatt

Hemant is a Product Manager with professional experience of 18+ years. Prior to Caizin, Hemant co-founded Tavisca. He transitioned from technology to product leadership over his career. He is a believer in an iterative approach to product development and greatly values customer first focus. His is passionate about building people centric organizations. When not working, he loves spending time with his wife and kids.

Mahendra YadavMahendra Yadav

A dedicated professional with 22+ years of experience in the IT industry, Mahendra has successfully played pivotal roles at every organization he is a part of.
Tavisca, his previous venture, developed into a successful product and technology company and was acquired by CxLoyalty, which eventually became a JPMorgan Chase company.
Mahendra is a Mechanical Engineering graduate of IIT Kanpur. With a strategic mindset and commendable business acumen, he has proven to be an accomplished and empathetic leader in technology, sales, business development, product management, and organizational culture.

Paritosh UpadhyayParitosh Upadhyay

Paritosh has steadily transitioned from being a QA engineer to a Delivery Manager with almost a decade long experience in various domains such as Insurance, Logistics, and Accounting.

Paritosh has demonstrated an exceptional ability to motivate, organize, lead, and coach scrum teams across multiple locations with a verifiable track record of managing multiple complex projects, delivering on time and within budget.

A petrol-head, Paritosh’s love for cars started with the fond memories of driving around in his father’s 1988 – Maruti 800. Paritosh also loves Lego building with his 4-year-old son and spending time with his family.

Rhishikesh ChinchoreRhishikesh Chinchore

As they say, “Design is thinking made visible,” Rhishikesh is a passionate Design Thinker.
He holds 12+ years of experience in the field of user experience (UX) design. He has worked in numerous domains like Travel, Medical, FinTech, etc. A people person, Rhishikesh loves to engage in an open exchange of ideas. He also believes that learning is a continual process.
Outside work, he conducts design thinking workshops, participates in Marathons, and is a curious traveler.

Shikha SingalShikha Singal

Shikha is a professionally qualified Digital Marketer with a master’s degree and over 20 years of dynamic experience in corporate strategy, marketing, and transforming business operations. Shikha is a motivated professional who excels at business expansion, mergers, and acquisitions.

She has consistently delivered impressive results throughout her career because of her exceptional organizational skills and specialized expertise in core product management, market research and branding, events, public relations, and key account management.

In her free time, Shikha loves painting, spending time with her loved ones, and visiting new scenic places.

Tanuj SaxenaTanuj Saxena

Tanuj is a Master of Computer Science with 16+ years of experience in technology, boasting a mix of core R&D and professional services.

Before Caizin, Tanuj co-founded an FSM analytics company and scaled the SaaS-based product’s technology using a cloud-first approach. He is passionate about building products, whether tech or non-tech. 

The gadget lover also loves going on long trips with his family.

81% of successful decision-makers

read valuable business content
with unique POVs

    We raise the bar for you!


    Talk to Us

    Raise the bar

    Careers

    Blog

    LinkedIn

    Contact Us

    Commune

    Join us

    Humans of Caizin

    Building the RIGHT product

    Discovery of User Needs

    Program over Project

    Roadmap of Outcomes

    Scale with Strategy

    Building the PRODUCT right

    Delivery Culture

    Technology Virtuosity

    Cross Functional Teams

    Led by Entrepreneurs

    Social Links

    Copyright © 2022 Caizin LLC | 4ESoftware Pvt Ltd
      Contact Us

            • test
            • Client & Partner Success Stories