如何将Autofac容器用作抽象工厂改造LetterFactory实现?
我想要修改当前的LetterFactory实现,用容器调用替代Activator.CreateInstance,以构造函数注入的方式完全初始化Letter。我在撰写此问题前已阅读了Autofac的注册文档和委托工厂文档,甚至相关Stack Overflow帖子,但仍未找到思路。
补充说明
IDocumentServicesCore是一个聚合根。- 所有
Letter都使用LetterTypeAttribute特性修饰(数量多达数百个)。 - 该
LetterFactory本身已在容器中注册。
当前LetterFactory代码:
public class LetterFactory : ILetterFactory { private readonly IDocumentServicesCore _documentServicesCore; public LetterFactory(IDocumentServicesCore documentServicesCore) { _documentServicesCore = documentServicesCore; } public LetterBase Create(int letterId) { if (letterId <= 0) { throw new ArgumentOutOfRangeException(nameof(letterId)); } List<Type> types = typeof(LetterBase).Assembly.GetTypes() .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase))) .ToList(); LetterBase letter = null; foreach(Type type in types) { LetterTypeAttribute attribute = type.GetCustomAttributes<LetterTypeAttribute>().First(); if (!attribute.LetterId.Contains(letterId)) { continue; } letter = Activator.CreateInstance(type, _documentServicesCore) as LetterBase; break; } if (letter != null) { return letter; } string message = $"Could not find a LetterBase to create for id {letterId}."; throw new NotSupportedException(message); } }
更新1
问题似乎源于Letter本身未注册,如何利用收集Letter的LINQ代码批量注册这些类型?
我来帮你梳理下解决思路,分两步走就能搞定:先批量注册所有Letter类型,再改造Factory借助容器来创建实例。
第一步:批量注册所有Letter类型
你可以在Autofac容器构建阶段,用你已经写好的LINQ逻辑找出所有Letter子类,然后批量注册它们。这样容器就能识别这些类型,自动处理它们的构造函数注入需求。
示例代码如下:
var builder = new ContainerBuilder(); // 注册聚合根IDocumentServicesCore builder.RegisterType<DocumentServicesCore>().As<IDocumentServicesCore>(); // 批量注册所有LetterBase的非抽象子类 var letterTypes = typeof(LetterBase).Assembly.GetTypes() .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase))); foreach (var type in letterTypes) { // 注册每个Letter类型,Autofac会自动处理构造函数依赖注入 builder.RegisterType(type).AsSelf(); } // 注册LetterFactory本身 builder.RegisterType<LetterFactory>().As<ILetterFactory>(); var container = builder.Build();
第二步:改造LetterFactory,用容器解析替代Activator.CreateInstance
现在需要给LetterFactory注入ILifetimeScope(Autofac的生命周期范围对象),通过它来解析对应的Letter实例——这样就不需要手动传_documentServicesCore了,容器会自动注入所有构造函数依赖。
同时我给你加了个性能优化:提前缓存Letter类型和LetterId的映射,避免每次创建实例都重复扫描反射,毕竟你有几百个Letter类型,重复扫描太浪费资源。
改造后的Factory代码:
public class LetterFactory : ILetterFactory { private readonly ILifetimeScope _lifetimeScope; // 提前缓存LetterId与对应类型的映射,提升性能 private readonly Dictionary<int, Type> _letterTypeMap; public LetterFactory(ILifetimeScope lifetimeScope) { _lifetimeScope = lifetimeScope; // 初始化类型映射缓存 _letterTypeMap = typeof(LetterBase).Assembly.GetTypes() .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase))) .SelectMany(t => { var attribute = t.GetCustomAttributes<LetterTypeAttribute>().First(); return attribute.LetterId.Select(id => new { Id = id, Type = t }); }) .ToDictionary(x => x.Id, x => x.Type); } public LetterBase Create(int letterId) { if (letterId <= 0) { throw new ArgumentOutOfRangeException(nameof(letterId)); } if (!_letterTypeMap.TryGetValue(letterId, out var letterType)) { string message = $"Could not find a LetterBase to create for id {letterId}."; throw new NotSupportedException(message); } // 用Autofac的生命周期范围解析实例,自动处理所有构造函数注入 return _lifetimeScope.Resolve(letterType) as LetterBase; } }
额外注意点
- 注入
ILifetimeScope而非直接注入IContainer是Autofac的最佳实践,避免持有根容器导致的内存泄漏问题,同时能利用当前的生命周期范围管理实例的生命周期。 - 缓存映射字典后,每次创建实例只需查字典,性能比原来每次扫描反射提升不少,尤其适合你有大量Letter类型的场景。
这样改造后,所有Letter的构造函数依赖都会由Autofac自动注入,完全替代了Activator.CreateInstance的手动传参方式,也解决了批量注册的问题。
内容的提问来源于stack exchange,提问作者Stephen Patten

