Inversion of Control (IoC) Principle

posted Originally published at medium.com 4 min read

Nowadays, it is very common for applications to use some pre-written code to perform specific tasks. Typically used to structure or provide functionality, this code can be reused and repurposed, avoiding the need to write it from scratch, saving time and effort. These useful codes are usually called Libraries and Frameworks, although they have fundamental differences, they are often confused. Do you know what the biggest difference is between a Library and a Framework? In this article, I’m going to talk about that difference, called Inversion of Control Principle.
Library vs Framework

First things first, let’s talk about the differences. A library is a set of functions or class that provides specific functionality. You can call it to do some work in your code flow and then it returns control to you. You call it and control how and when it gets called. Below is a C code that calls the <math.h> library to calculate the square root. Since we have flow control, we call the method, the already implemented method does the calculation for us, and finally, we show the result.

#include <stdio.h> 
#include <math.h>
  
int main() {
  double number = 16;
  double square_root = sqrt(number);
  printf("%.2lf", square_root);
  return 0;
}

In a different way, a framework works, seen as a skeleton, with some custom behavior that we need to insert and it will call. We just implement a functionality and it calls, but we don’t know how and exactly when, for this reason we lose control, we are inverting control — the framework calls the implementation, instead of the application calling it. Below, we have an example of a framework called Flask, used to create a web server in a simple and extremely powerful way, abstracting aspects such as configurations, sockets and HTTP requests. We don’t worry about this, because the framework does it for us, providing a set of functionalities, widgets and ready-made structures to facilitate development.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return "<h1>Hello, Flask!</h1><p>Welcome to the home page.</p>"

@app.route('/about')
def about():
    return "<h1>About</h1><p>This is a simple Flask app.</p>"

if __name__ == '__main__':
    app.run(debug=True)

As you read, inversion of control is a fundamental characteristic of framework, now let’s talk more about this principle.
Inversion of Control Principle (IoC)

As I said before, inversion of control consists of changing the flow of control from the usual flow used in procedural programming and putting external code to manage it. This external code, usually a framework, calls user-provided methods to do something and these methods return control to the framework, or whatever. When you use this framework, it usually plays the role of the main one that defines the skeleton of the step sequence. This concept reminds me of the Template Method design pattern, which is a way to implement the inversion of control principle. In it we defines the template of algorithm in a superclass and subclasses override methods without changing its structure. Did you understand? We implemented it but we don’t know how the template will execute the algorithm.

abstract class Game {
    public final void play() {
        initialize();
        startPlay();
        endPlay();
    }

    protected abstract void initialize();
    protected abstract void startPlay();
    protected abstract void endPlay();
}

class Soccer extends Game {
    @Override
    protected void initialize() {
        System.out.println("Setting up Soccer field...");
    }

    @Override
    protected void startPlay() {
        System.out.println("Kickoff! Match started.");
    }

    @Override
    protected void endPlay() {
        System.out.println("Full time. Match ended.");
    }
}

public class Main {
    public static void main(String[] args) {
        Game soccer = new Soccer();

        System.out.println("\n=== Playing Soccer ===");
        soccer.play();
    }
}

Above, the Game class creates the behavior model. Subclasses, such as Soccer, only implement the content but do not decide the order of execution, and then call the play method, which controls the flow of the application and calls the other methods.
Framework examples

In Flutter, a UI framework, we abstract a lot of things, as it provides a structure, only needing to fill in the customizable parts.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("User Profile")),
        body: Center(child: Text("Hello, there!")),
      ),
    );
  }
}

In PetalTest, a testing framework I’m developing in Dart inspired by JUnit, we can implement the setUp and tearDown methods, however, we do not call this, the framework will call the implemetation that has been provided by the application. Again, the inversion of control used here is that instead of the application calling a framework method, we delegate this responsability to the framework. If you’re curious about JUnit, check out this article, and I’ll be writing about PetalTest soon.

///PetalTest Framework Example: We don't call this, we just implement it
@ClassTestable()
class ClassTest {

  @BeforeEach()
  void setUp() {
    print("Before Each Test");
  }

  @AfterEach()
  void tearDown() {
    print("After Each Test");
  }
}

In conclusion, this principle gives us the power to separate components, making them more flexible, and also guarantees low coupling — Even more so using dependency injection, another way to implement IoC.

Did you get a better understanding of Inversion of Control? Do you have any questions or find any errors? Let me know, comment below. See you later!

If you read this far, tweet to the author to show them you care. Tweet a Thanks

Nice clear explanation, Victor! Thanks for breaking down IoC with code examples — really helps to grasp the concept. Quick question: how do you see IoC playing out differently in functional programming compared to OOP frameworks?

Great question, I explored little about this in functional programming, I can only imagine that this is done through the explicit passage of dependencies, but less abstract and visible.

More Posts

Facade Pattern provides a simplified interface to complex subsystems, hiding implementation details behind a clean API.

Hussein Mahdi - Apr 9

Timeless software principles are vital to guide the speed and risks of modern AI-driven development.

Matheus Ricardo - Aug 19

Architectural Analysis of JUnit

Victor Lopes - Jul 12

State Pattern: Transforming Objects Through Internal States

Hussein Mahdi - May 7

Lessons from a Year of Side Projects: What Actually Worked (and What Didn’t)

Sourav Bandyopadhyay - Jun 24
chevron_left