Skip to content

Instantly share code, notes, and snippets.

@MangaD
Created February 9, 2025 18:19
Show Gist options
  • Select an option

  • Save MangaD/46edc8054354e7065c5e9cc9aea5ea3c to your computer and use it in GitHub Desktop.

Select an option

Save MangaD/46edc8054354e7065c5e9cc9aea5ea3c to your computer and use it in GitHub Desktop.
Dependency Injection

Dependency Injection

CC0

Disclaimer: ChatGPT generated document.

Dependency Injection (DI) is a software design pattern in which an object (or module) receives its dependencies (other objects it depends on) from an external source rather than creating them itself. This promotes flexibility, modularity, and testability in your code.


Key Concepts

  1. Dependency:

    • A dependency is any object or resource that a class relies on to perform its work.
    • For example, if a UserService needs a Database to fetch user data, the Database is a dependency of the UserService.
  2. Injection:

    • Instead of the class creating its own dependencies, they are injected by an external system, such as a dependency injection framework or manually by the programmer.

Why Use Dependency Injection?

  • Loose Coupling: Classes are decoupled from their dependencies, making them more modular.
  • Testability: Makes unit testing easier by allowing mock or stub objects to replace real dependencies.
  • Flexibility: Dependencies can be swapped at runtime, enabling different configurations for production, testing, or development.
  • Maintainability: Changes to a dependency don’t require changes to the dependent class.

How Dependency Injection Works

Instead of a class creating its dependencies internally, they are provided externally. Dependency injection can be achieved through different methods:

1. Constructor Injection

Dependencies are passed into the class through its constructor.

class Database {
public:
    void query() {
        // Database query logic
    }
};

class UserService {
private:
    Database& db;
public:
    UserService(Database& database) : db(database) {}

    void getUser() {
        db.query();
        // Fetch user logic
    }
};

// Usage
Database db;
UserService userService(db);
userService.getUser();
  • Advantage: Dependencies are clear and cannot be changed after the object is constructed.

2. Setter Injection

Dependencies are set through public setter methods.

class UserService {
private:
    Database* db = nullptr;
public:
    void setDatabase(Database* database) {
        db = database;
    }

    void getUser() {
        if (db) db->query();
        // Fetch user logic
    }
};

// Usage
Database db;
UserService userService;
userService.setDatabase(&db);
userService.getUser();
  • Advantage: Dependencies can be modified after the object is created.
  • Disadvantage: Risk of the object being in an incomplete state if the dependency isn’t set.

3. Interface Injection

The dependency provides the method for injection.

class IDatabase {
public:
    virtual void query() = 0;
    virtual ~IDatabase() = default;
};

class Database : public IDatabase {
public:
    void query() override {
        // Database query logic
    }
};

class UserService {
private:
    IDatabase* db;
public:
    void setDatabase(IDatabase* database) {
        db = database;
    }

    void getUser() {
        db->query();
    }
};

// Usage
Database db;
UserService userService;
userService.setDatabase(&db);
userService.getUser();
  • Advantage: Ensures polymorphism and allows for substituting implementations (e.g., mocks for testing).

Dependency Injection in Practice

Dependency injection is often managed by DI frameworks that automatically resolve and inject dependencies. Examples include:

  • C++: Boost.DI
  • Java: Spring Framework, Google Guice
  • Python: dependency-injector, Flask's app.config

Advantages of Dependency Injection

  1. Loose Coupling:
    • The dependent class doesn’t need to know how the dependency is constructed.
  2. Testability:
    • Dependencies can be replaced with mocks or stubs for testing.
  3. Flexibility:
    • Different implementations of a dependency can be injected without changing the dependent class.
  4. Reusability:
    • Classes become reusable with different dependencies.

Disadvantages of Dependency Injection

  1. Increased Complexity:
    • Setting up DI frameworks or managing dependencies manually can add complexity.
  2. Overhead:
    • Can add performance overhead if the DI container is complex.
  3. Debugging Challenges:
    • Dependency injection frameworks can make debugging more difficult due to abstraction.

Example Without DI (Tightly Coupled)

class UserService {
private:
    Database db; // Tightly coupled to Database
public:
    void getUser() {
        db.query();
    }
};
  • Problem: The UserService class is tightly coupled to the Database class, making it difficult to swap or mock dependencies.

Example With DI (Loosely Coupled)

class UserService {
private:
    Database& db;
public:
    UserService(Database& database) : db(database) {}

    void getUser() {
        db.query();
    }
};
  • Solution: The Database dependency is injected, allowing for easier testing and swapping.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment