Appearance
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 testingType 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
| Constructor | Setter | Interface | |
|---|---|---|---|
| When injected | Object creation | After creation | After creation |
| Required deps | Best | Can forget | Enforced |
| Optional deps | Forced | Best | Overhead |
| Change at runtime | No | Yes | Yes |
| Most common | Yes | Sometimes | Rare |
Key Points
| Point | Detail |
|---|---|
| Core idea | Receive dependencies, don't create them |
| Depends on | Abstractions not concrete classes |
| Most common type | Constructor injection |
| Main benefit | Loose coupling + easy testing |
| Relates to | Dependency 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."