Robert C. Martin in his book "Clean Architecture: A Craftsman's Guide to Software Structure and Design" mentions that a good architecture allows to delay decisions about details. One of those he mentions is:
It is not necessary to adopt a dependency injection framework early in development, because the high-level policy should not care how dependencies are resolved
What is a proper work methodology to achive that? How can you start developing in an efficient way without a particular dependency injection framework?
How can you start developing in an efficient way without a particular dependency injection framework?
The short answer is:
Just like you did before those DI frameworks exists!
Even those frameworks have a lot of benefits it's a quite good idea to start without them to write your code in a framework independent way. This addresses the code smell immobility.
Let's assume you want to setup a controller, a use case and repository.
+------------+ +----------+ +------------+
| Controller | ---> | Use Case | ---> | Repository |
+------------+ +----------+ +------------+
Just use standard constructors and setters.
Repository repo = new RepositoryImpl();
UseCase useCase = new UseCaseImpl(repo);
Controller controller = new ControllerImpl(useCase);
Note: I don't prefer the Impl
ending. It's just to distinguish between interface and implementation in this example.
The piece of code I showed above is just the same as what a DI framework does.
If you want to use a DI framework you can then wrap the code above in method that the DI framework uses. In java spring we would call such methods factory methods. E.g.
@Bean
public Controller controller(){
Repository repo = new RepositoryImpl();
UseCase useCase = new UseCaseImpl(repo);
return new ControllerImpl(useCase);
}
Maybe the UseCase
should be a singleton and therefore should be placed in a separate factory method.
@Bean
public UseCase useCase(DataSource ds){
Repository repo = new RepositoryImpl(ds);
repo.setCacheEnabled(true);
return new UseCaseImpl(repo);
}
@Bean
public Controller controller(UseCase useCase){
ControllerImpl controller = new ControllerImpl(useCase);
SomeConfig c = ...;
controller.setSomeConfig(c);
return controller;
}
The main point is that the controller, use case and repository in my example only tell what they need and not where it come from. It's a good idea to separate the setup code or bootstrap code from the other.