I would like to use factory-based dependency injection without passing any "kernel"-container so that it's impossible to instantiate a class without having its dependencies explicitely passed from the "top".
The manual way to do so requires code like this in a bootstrap:
static void Main(string[] args)
{
// simplified example, can require classes in reality
ABFactory abFactory = (data1) => new AB(data1);
ACAFactory acaFactory = (data1) => new ACA(data1);
ACFactory acFactory = (x) => new AC(x, acaFactory);
IA a = new A(1, new AA(1, new AAA(), new AAB()), abFactory, acFactory);
a.Action(123);
}
When factories are defined as
delegate IAB ABFactory(string data1);
delegate IAC ACFactory(int x);
delegate IACA ACAFactory(int data1);
Is there anything I can use to make factory building easier or even automatic? With different factory types support (pool, ThreadLocal cache, etc)?
UPDATE
Some real code example:
public interface IItemSetSpawnController
{
void TransitSpawned();
}
public class ItemSetSpawnController : IItemSetSpawnController
{
readonly GameMap.ItemSet _set;
readonly LootableFactoryDelegate _lootableFactory;
readonly IFiber _fiber;
readonly int _defaultRespawnTime;
public ItemSetSpawnController([NotNull] GameMap.ItemSet set, int defaultRespawnTime, [NotNull] LootableFactoryDelegate lootableFactory, IFiber fiber)
{
if (set == null) throw new ArgumentNullException(nameof(set));
if (lootableFactory == null) throw new ArgumentNullException(nameof(lootableFactory));
if (set.Items.Count == 0) throw new ArgumentException("Empty set", nameof(set));
_set = set;
_lootableFactory = lootableFactory;
_fiber = fiber;
_defaultRespawnTime = defaultRespawnTime;
}
public void TransitSpawned()
{
// Fiber.Schedule for respawning
}
}
public delegate IItemSetSpawnController ItemSetSpawnControllerFactory(
[NotNull] GameMap.ItemSet set, int defaultRespawnTime, [NotNull] LootableFactoryDelegate lootableFactory, IFiber fiber);
protected virtual void AddMapLootables()
{
foreach (var set in ItemSets)
{
if (set.Items.Count == 0) continue;
var c = ItemSetSpawnControllerFactory(
set,
Settings.LootRespawnTime,
LootableFactory,
ExecutionFiber);
c.TransitSpawned();
}
}
It seems to me that your components are initialized with runtime data. This is bad practice, because this will complicate your DI configuration a lot, and I think that this is the case in your case.
Instead of using a factory to initialize a component where the component's constructor depends on both service dependencies and runtime data, move the runtime data out of the constructor and supply it to the component at runtime. You can either do this by passing the runtime value through the component's method, or you can inject a service into the component that allows the component to request that value at runtime (after initialization).
Which option to choose, depends on whether the use of the runtime value is an implementation detail. If it's an essential part of the abstraction, the value should be part of the abstraction's method signature. If it's of no concern to the abstraction, and only the component itself should know about it, injection of a service that allows access to this runtime value is the most obvious solution.
UPDATE
If you move runtime data out of the constructors you will be able to remove the need to use the factory as extra layer of abstraction and you can change the code to something like this:
public interface IItemSetSpawnController {
void TransitSpawned(GameMap.ItemSet set);
}
public class ItemSetSpawnController : IItemSetSpawnController {
readonly LootableFactoryDelegate _lootableFactory;
readonly IFiber _fiber;
readonly ISessings settings;
public ItemSetSpawnController(ISessings settings,
[NotNull] LootableFactoryDelegate lootableFactory, IFiber fiber) {
_lootableFactory = lootableFactory;
_fiber = fiber;
_settings = settings;
}
public void TransitSpawned([NotNull] GameMap.ItemSet set) {
if (set == null) throw new ArgumentNullException(nameof(set));
// If LootRespawnTime is a config value that doesn't change after startup, you
// can more it back into the constructor of the controller.
int defaultRespawnTime = _settings.LootRespawnTime;
// Fiber.Schedule for respawning
}
}
class MapLooter {
IItemSetSpawnController controller;
public MapLooter(IItemSetSpawnController controller){
this.controller = controller;
}
public void AddMapLootables() {
foreach (var set in ItemSets) {
if (set.Items.Count == 0) continue;
this.controller.TransitSpawned(set);
}
}
}