构造函数 DI 的自动工厂生成器
Automatic factory generators for constructor DI
我想在不传递任何 "kernel" 容器的情况下使用基于工厂的依赖项注入,这样就不可能实例化 class 而不从 "top" 显式传递其依赖项.
手动方法需要这样的代码 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);
}
当工厂定义为
delegate IAB ABFactory(string data1);
delegate IAC ACFactory(int x);
delegate IACA ACAFactory(int data1);
有什么可以让工厂建设更简单甚至自动化的方法吗?支持不同的工厂类型(池、ThreadLocal 缓存等)?
更新
一些真实的代码示例:
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();
}
}
在我看来,您的组件是用运行时数据初始化的。这是bad practice,因为这会使你的DI配置复杂化很多,我认为你的情况就是这样。
不要使用工厂来初始化组件的构造函数同时依赖于服务依赖项和运行时数据的组件,而是将运行时数据移出构造函数并在运行时将其提供给组件。您可以通过组件的方法传递运行时值来完成此操作,也可以将服务注入组件,允许组件在运行时(初始化后)请求该值。
选择哪个选项取决于运行时值的使用是否是一个实现细节。如果它是抽象的重要部分,那么该值应该是抽象方法签名的一部分。如果它与抽象无关,并且只有组件本身应该知道它,那么注入允许访问此运行时值的服务是最明显的解决方案。
更新
如果您将运行时数据移出构造函数,您将能够消除使用工厂作为额外抽象层的需要,并且您可以将代码更改为如下内容:
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);
}
}
}
我想在不传递任何 "kernel" 容器的情况下使用基于工厂的依赖项注入,这样就不可能实例化 class 而不从 "top" 显式传递其依赖项.
手动方法需要这样的代码 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);
}
当工厂定义为
delegate IAB ABFactory(string data1);
delegate IAC ACFactory(int x);
delegate IACA ACAFactory(int data1);
有什么可以让工厂建设更简单甚至自动化的方法吗?支持不同的工厂类型(池、ThreadLocal 缓存等)?
更新
一些真实的代码示例:
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();
}
}
在我看来,您的组件是用运行时数据初始化的。这是bad practice,因为这会使你的DI配置复杂化很多,我认为你的情况就是这样。
不要使用工厂来初始化组件的构造函数同时依赖于服务依赖项和运行时数据的组件,而是将运行时数据移出构造函数并在运行时将其提供给组件。您可以通过组件的方法传递运行时值来完成此操作,也可以将服务注入组件,允许组件在运行时(初始化后)请求该值。
选择哪个选项取决于运行时值的使用是否是一个实现细节。如果它是抽象的重要部分,那么该值应该是抽象方法签名的一部分。如果它与抽象无关,并且只有组件本身应该知道它,那么注入允许访问此运行时值的服务是最明显的解决方案。
更新
如果您将运行时数据移出构造函数,您将能够消除使用工厂作为额外抽象层的需要,并且您可以将代码更改为如下内容:
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);
}
}
}