This is a port of the FactorIt .NET dependency injection container to C++. This port is designed to behave and feel just like the original. It uses the same fluent interface as the .NET version and strive to offer a similar feature set.
Take a look at the FactorIt repository to get a better idea of where this is going.
Feature | .NET | C++ |
---|---|---|
No magic | ✓ | ✓ |
Fluent interface | ✓ | ✓ |
Read-only interface | ✓ | ✓ |
Write-only interface | ✓ | ✓ |
Templated/Generic binding | ✓ | ✓ |
Templated/Generic unbinding | ✓ | |
Templated/Generic resolving | ✓ | ✓ |
Service keys | ✓ | ✓ |
Decorators | ✓ | |
Notifications | ✓ | ✓ |
Child containers | ✓ | |
Resolving scope | ✓ | |
Auto cleanup | ✓ | 1 |
- Partially supported. Service destructor will be called when shared_ptr dies.
To create a root container, simply use the CreateRoot()
static method. This ensures that root containers are clearly identified in your code.
auto container = Container::CreateRoot();
Then, you can simply bind any interface to a factory using the Bind/To
fluent syntax provided by the container.
container
->Bind<IService>()
->To([](IServiceLocator* l) {
return std::make_shared<ConcreteService>();
});
Since this is a little long to write, we also provide you with a really simple shortcuts for services that have a default constructor.
container
->Bind<IService>()
->To<ConcreteService>();
If you need to pass the container around, you can either use the IBindingRoot
interface for a write only container or the IServiceLocator
interface for a read only container.
To extract a dependency from the container, you can simply use the Resolve
method. On the first call, this will create an instance of the bound type using the provided factory. Subsequent calls will resolve to the same instance. Resolving is a thread safe operation.
auto aConcreteService = container
->Resolve<IService>();
If the service might not yet be registered in the container, you can use the ResolveOrDefault
method that will ensure that a customizable default value is returned.
auto aConcreteService = container
->ResolveOrDefault<IService>([]() {
return std::shared_ptr<IService>(nullptr);
});
As the amount of services in the container grows, you might see the need to register the same interface multiple times but with different factories. For instance, when using Reactive Extensions, you might want to register the scheduler_interface
interface for each type of schedulers your application requires. The Bind
method enables you to provide an optional key parameter for those cases.
container
->Bind<scheduler_interface>("background")
->To<new_thread>();
container
->Bind<scheduler_interface>("immediate")
->To<immediate>();
You can also provide a default instance, when no key is provided, or an alias by using the service locator provided as the first parameter of the factory.
container
->Bind<scheduler_interface>()
->To([](IServiceLocator* l) {
return l->Resolve<scheduler_interface>("background")
});
In some rare cases, you will want to remove a specific service from the container. For this, you can use the Unbind
method.
container
->Unbind<scheduler_interface>("background");
Sometimes, you will also need to schedule operations to be executed when a specific contract is registered. This could be useful if you insert plugins dynamically into the container or if you simply need to notify your log manager that a new logging destination has been registered. This can be done using the Postpone
method.
container
->Postpone<ILogDestination>([](std::shared_ptr<ILogDestination> dest) {
LogManager.Add(dest);
});