依赖注入与简单接口使用有何区别?
Hey there! Great question—let’s break this down using your code as a starting point, since you’ve already got the foundation of interfaces right.
First, let’s clarify: what you’ve written is a solid use of interfaces to abstract logging behavior, but it’s not yet dependency injection (DI). The key difference boils down to how your code gets access to the ILogger instance and who’s in control of that dependency.
Let’s break down the core distinctions:
1. Who controls the dependency?
- Your current setup uses a global
ILoggervariable inApplication.h. That means any code in the application has to reach out to this global to use logging. The application itself doesn’t receive the logger—it has to go fetch it. - With dependency injection, the dependency (your
ILoggerimplementation) is passed into the code that needs it, rather than being pulled from a global, singleton, or hardcoded source. For example, instead of relying on a global, yourApplicationclass would take anILoggeras a constructor parameter:
Then, when you start your app, you create the logger first and pass it in:class Application { private: ILogger& m_logger; // Hold a reference to the injected logger public: // Inject the logger through the constructor Application(ILogger& logger) : m_logger(logger) {} void run() { m_logger.writeMsg("Application started!"); // ... rest of your app logic } };int main() { Application_Logger appLogger; Application myApp(appLogger); // Inject the logger here myApp.run(); // For testing, you'd do this instead: // Test_Logger testLogger; // Application testApp(testLogger); // testApp.run(); }
2. Coupling between components
- Your global approach keeps your
Applicationtightly coupled to that global variable. Even though you’re using an interface, changing which logger is used requires modifying the global’s assignment (or relying on some external code to set it). If you ever need multiple logger instances (e.g., one for business logic, one for errors), you’d have to add more globals—messy and hard to maintain. - DI eliminates this tight coupling. Your
Applicationonly cares that it gets something that implementsILogger; it has no idea whether it’s anApplication_Logger,Test_Logger, or even a newFile_Loggeryou write later. You can swap implementations without touching theApplicationcode at all.
3. Testability
- With your global setup, testing the
Application’s logging behavior is tricky. You’d have to set the globalloggerto aTest_Loggerbefore running tests, then reset it afterward—easy to forget, and risky if tests run in parallel (they might overwrite each other’s logger). - With DI, testing is trivial. You just pass a
Test_Loggerdirectly to theApplicationinstance in your test code. No globals to mess with, no state leaks between tests—each test gets its own isolated logger.
4. Flexibility and scalability
- Global interfaces work for small apps, but they fall apart as your code grows. What if you need different loggers for different parts of the app? Or want to configure logging based on environment variables? Globals make this hard.
- DI lets you handle these scenarios seamlessly. You can create different logger instances for different components, configure them dynamically at startup, or even swap implementations while the app is running—all without changing the code that uses the logger.
To sum it up:
Using an interface is about defining a contract for behavior. Dependency injection is about how you deliver an implementation of that contract to the code that needs it. Your current code uses the contract, but it’s still relying on a global to get the implementation. DI flips that: the implementation is given to the code, rather than the code going to find it.
This might feel like a small change at first, but it makes your code far more modular, testable, and easy to maintain as it grows.
内容的提问来源于stack exchange,提问作者Dominique

