Skip to content

Dependency Injection ​

Instead of a class creating its own dependencies, those dependencies are provided from outside.

Don't create what you need — ask for it or receive it.

Think: Making your own coffee at work (bad) vs Coffee machine provided by the office (good).

Problem Without DI ​

cpp
class UserService {
    MySQLDatabase db; // created internally — tightly coupled to MySQL!
public:
    void saveUser(string name) { db.save(name); }
    // what if we want MongoDB? must change UserService!
};

Define Abstraction First ​

cpp
class IDatabase {
public:
    virtual void save(string data) = 0;
    virtual ~IDatabase() {}
};

class MySQLDatabase : public IDatabase { void save(string d) override {/*...*/} };
class MongoDB : public IDatabase { void save(string d) override {/*...*/} };
class MockDatabase : public IDatabase { void save(string d) override {/*...*/} }; // for testing

Type 1: Constructor Injection (Most Common) ​

cpp
class UserService {
    IDatabase* db; // depends on abstraction
public:
    UserService(IDatabase* d) : db(d) {} // dependency injected via constructor
    void saveUser(string name) { db->save(name); }
};

MySQLDatabase mysql;
UserService service1(&mysql); service1.saveUser("Alice"); // MySQL

MongoDB mongo;
UserService service2(&mongo); service2.saveUser("Bob"); // MongoDB

MockDatabase mock;
UserService service3(&mock); service3.saveUser("Test"); // Mock (testing)

Best for required dependencies — object can't work without them.

Type 2: Setter Injection ​

cpp
class UserService {
    IDatabase* db = nullptr;
public:
    void setDatabase(IDatabase* d) { db = d; } // inject via setter
    void saveUser(string name) {
        if (!db) { cout << "No database set!" << endl; return; }
        db->save(name);
    }
};

UserService service;
service.setDatabase(&mysql); service.saveUser("Alice"); // MySQL
service.setDatabase(&mongo); service.saveUser("Bob");    // swap to MongoDB!

Best for optional dependencies — can be changed at runtime.

Type 3: Interface Injection ​

cpp
class IDatabaseConsumer {
public:
    virtual void setDatabase(IDatabase* db) = 0; // forces injection
};

class UserService : public IDatabaseConsumer {
    IDatabase* db = nullptr;
public:
    void setDatabase(IDatabase* d) override { db = d; }
    void saveUser(string name) { db->save(name); }
};

Less common — used when injection must be enforced by contract.

DI Makes Testing Easy ​

cpp
class MockDatabase : public IDatabase {
public:
    vector<string> saved;
    void save(string data) override { saved.push_back(data); } // no real DB call
};

void testOrderService() {
    MockDatabase db; MockLogger logger; MockEmailSender emailer;
    OrderService order(&db, &logger, &emailer);
    order.placeOrder("alice@mail.com", "Laptop");
    assert(db.saved[0] == "Laptop"); // verify behavior
    cout << "All tests passed!" << endl;
}

3 Types Comparison ​

ConstructorSetterInterface
When injectedObject creationAfter creationAfter creation
Required depsBestCan forgetEnforced
Optional depsForcedBestOverhead
Change at runtimeNoYesYes
Most commonYesSometimesRare

Key Points ​

PointDetail
Core ideaReceive dependencies, don't create them
Depends onAbstractions not concrete classes
Most common typeConstructor injection
Main benefitLoose coupling + easy testing
Relates toDependency Inversion (SOLID D)

One-liner: "Dependency Injection means providing a class its dependencies from outside rather than creating them internally — achieved via constructor, setter, or interface injection — enabling loose coupling, flexibility, and easier testing."