Dependency Injection with C++20 Concepts and the Service Provider Pattern
In this article I explore a system of using C++20 Concepts for Dependency Injection
Linux /g++ 11.1.0
In this article I explore a system of using C++20 Concepts for Dependency Injection. A working example can be found here
For each service you will need:
- an interface Concept
- a provider Concept
- concrete implementation
Optionally you could also create a mock implementation using the testing framework of your choice.
End Game
In the sample code I have several functions (all, two, onlyLogger, onlyPersist, onlyCalculator) that have different injection requirements. I have covered requirements for all, a sample/mix (two), and a single service. All functions take a provider object as their only parameter.
Here are the functions and their requirements:
- all (logger, persistence, calculator)
- two (logger, persistence)
- onlyLogger (logger)
- onlyPersist (persistence)
- onlyCalculator (calculator)
void all(ServiceProviderInterface auto provider) {
provider.logger.info("Hello Logger DI: all");
std::cout << "add (all) = " << provider.calculator.add(1,2) << std::endl;
}
template<typename T>
concept TwoProvider =
LoggerServiceProvider<T> &&
DataPersistenceProvider<T>;
void two(TwoProvider auto provider) {
provider.logger.info("Hello Logger DI: two");
}
void onlyLogger(LoggerServiceProvider auto provider) {
provider.logger.info("Hello Logger DI: onlyLogger");
}
void onlyPersist(DataPersistenceProvider auto provider) {
provider.persistence.write();
}
void onlyCalculator(CalculatorProvider auto provider) {
std::cout << "add (onlyCalculator) = " << provider.calculator.add(3,4) << std::endl;
}
All requirements can use the ServiceProviderInterface directly, single requirements can use the individual provider directly. The more useful case is a mix of services which requires a new concept to be used. Luckily the mixed concepts are easy to write by using &&
with the services you require.
Service Provider
Creating the Services
This is the Calculator implementation. It's simply a struct with 2 functions.
We can then create a Concept for Calculator that enforces the interface at compile time.
The provider allows us to simplify the Service Provide Concept and enforce the variable name of this service (in this case calculator
).