055
creational behavioral

Dependency Injection

Reference Wikipedia ↗
Dependency Injection — class diagram
Plate 055 class diagram

Dependency Injection (DI) is a software design pattern that implements the Inversion of Control (IoC) principle for resolving dependencies. Instead of a component creating its dependencies, or directly looking them up, those dependencies are injected into the component. This promotes loose coupling, making the code more modular, reusable, and testable. DI leads to more maintainable and flexible applications, as changes to one part of the system are less likely to cascade through other parts.

Usage

Dependency Injection is a widely used pattern in modern software development. It’s commonly employed in:

  • Frameworks: Many frameworks (like Spring, Angular, and .NET) have built-in DI containers to manage object dependencies.
  • Testing: DI makes unit testing easier by allowing you to inject mock dependencies, isolating the component under test.
  • Large Applications: For complex projects, DI helps manage the relationships between numerous components, improving overall structure and maintainability.
  • Microservices: Loosely coupled microservice architectures inherently benefit from dependency injection.

Examples

  1. Spring Framework (Java): Spring’s core feature is its DI container. Developers define beans (objects) and their dependencies declaratively (through XML configuration or annotations like @Autowired). Spring then automatically resolves and injects these dependencies when it creates the beans. This makes application components highly configurable and testable.

  2. Angular (TypeScript): Angular uses a hierarchical dependency injection system. Components declare their dependencies in their constructors, and the Angular injector provides those dependencies. Angular’s dependency injection simplifies development and promotes modularity, enhancing code reusability and maintainability. For example, an HttpClient service can be injected into any component that needs to make HTTP requests.

Specimens

15 implementations
Specimen 055.01 Dart View specimen ↗

Dependency Injection (DI) is a design pattern that promotes loose coupling by providing dependencies to a class instead of the class creating them itself. This improves testability, maintainability, and reusability. The code demonstrates DI using constructor injection. A DataSource interface defines a data retrieval contract, with a concrete RemoteDataSource implementation. The Repository class, instead of creating a DataSource itself, receives an instance of it through its constructor. This allows easy swapping of the data source – crucial for testing with mocks or providing different data in various environments. This approach is idiomatic Dart as it leverages interfaces and constructors for clear dependency management and testability, aligning with modern Dart development practices.

// Define the interface for the data source.
abstract class DataSource {
  Future<String> getData();
}

// Concrete implementation of the data source (e.g., fetching from a remote API).
class RemoteDataSource implements DataSource {
  @override
  Future<String> getData() async {
    await Future.delayed(Duration(seconds: 1)); // Simulate network delay
    return 'Data from the remote source!';
  }
}

// A mock DataSource for testing.
class MockDataSource implements DataSource {
  @override
  Future<String> getData() async {
    return 'Mock data for testing!';
  }
}

// Repository class that depends on a DataSource.
class Repository {
  final DataSource dataSource;

  // Constructor injection: receive the dependency.
  Repository(this.dataSource);

  Future<String> fetchData() async {
    return await dataSource.getData();
  }
}

// Example Usage
void main() async {
  // Inject the real data source.
  final remoteDataSource = RemoteDataSource();
  final repository = Repository(remoteDataSource);
  final data = await repository.fetchData();
  print('Real Data: $data');

  // Inject the mock data source (for testing).
  final mockDataSource = MockDataSource();
  final testRepository = Repository(mockDataSource);
  final testData = await testRepository.fetchData();
  print('Mock Data: $testData');
}