How does IoC help reduce coupling between components?
Inversion of Control (IoC) reduces coupling between components by separating an object's logic from the creation and management of its dependencies. This is achieved because the object no longer knows who and how creates the dependencies it needs - it simply receives them "ready-made" through interfaces or external mechanisms (a framework, a container).
1. Before: tight coupling
Without IoC, each component creates its own dependencies:
class UserService:
def __init__(self):
self.repository = UserRepository()The problem:
UserServicedepends on the concrete classUserRepository.- If the implementation needs to be replaced (for example, with
CachedUserRepository), the code ofUserServicehas to change. UserServicecannot be tested in isolation, because it always creates a specific repository.
This is tight coupling - the classes are "glued" to each other.
2. After applying IoC: loose coupling
With IoC, dependencies are injected from outside, usually through the constructor:
class UserService:
def __init__(self, repository):
self.repository = repositoryNow UserService only knows that it needs an object with a find_by_id method, not which one exactly.
A container or external code decides which implementation to plug in, for example:
user_service = UserService(CachedUserRepository())The result:
- The
UserServiceclass does not depend on specific implementations. - It can be tested by plugging in a fake repository.
- The storage logic can be changed without changing
UserService.
3. IoC and abstractions
IoC forces modules to depend on abstractions, not on details. For example:
class Repository(Protocol):
def find_by_id(self, user_id): ...
class UserService:
def __init__(self, repository: Repository):
self.repository = repositoryNow both UserService and the various Repository implementations depend on a single abstraction.
This removes direct connections between the layers of the system.
4. Effect on the architecture
IoC makes the system modular:
- Components can be changed independently.
- It is easier to add new implementations (for example, a repository for another database).
- The number of changes needed when modifying code decreases (localization of changes).
- Testability increases: "stubs" or "mocks" can be plugged in.
5. Practical consequence
When IoC is used:
- Dependencies are inverted: the code does not call the infrastructure, the infrastructure injects dependencies into the code.
- Each component is responsible only for its own logic, without worrying about creating or configuring other parts.
- Coupling drops from "A knows B" to "A knows only the abstraction, not the concrete B".
Summary: IoC reduces coupling between components because objects no longer create or manage their own dependencies. Instead, they work through abstractions, and specific implementations are supplied from outside. This weakens connections, simplifies replacing components, and increases the system's flexibility and testability.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.