如何正确获取IPrinterRepository接口的对应实现?
优化思路:告别Switch和硬编码全类名
我太懂这种纠结了——每次加新打印机实现都要改switch分支、维护常量字符串,烦得要死;直接存全类名又觉得像埋了个定时炸弹,哪天重构类名或命名空间,数据库里的记录全失效。给你两个不用新增额外依赖的优化方案,都是基于.NET原生能力的:
方案1:特性标记+自动映射(推荐频繁新增实现的场景)
核心思路是用自定义特性给每个实现类打标签,然后在启动时自动扫描并构建映射表,彻底告别手动维护switch和常量。
步骤1:定义标记特性
先写一个特性类,用来绑定打印机标识和实现类:
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class PrinterRepositoryAttribute : Attribute { public string PrinterCode { get; } public PrinterRepositoryAttribute(string printerCode) { PrinterCode = printerCode; } }
步骤2:给实现类打标记
给每个IPrinterRepository的实现类加上这个特性,关联对应的打印机编码:
// 比如原来的Epson实现 [PrinterRepositoryAttribute(PrinterCode.Epson)] public class EpsonRepository : IPrinterRepository { // 你的实现逻辑 } [PrinterRepositoryAttribute(PrinterCode.HP)] public class HPRepository : IPrinterRepository { // 你的实现逻辑 }
步骤3:自动注册映射与服务
在ConfigureServices里,扫描程序集自动构建映射表,同时注册所有实现类:
var assembly = Assembly.GetExecutingAssembly(); // 扫描所有IPrinterRepository的实现类,构建编码→类型的映射 var printerRepoMap = assembly.GetTypes() .Where(t => typeof(IPrinterRepository).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) .ToDictionary( t => t.GetCustomAttribute<PrinterRepositoryAttribute>()?.PrinterCode ?? throw new InvalidOperationException($"类{t.Name}未标记PrinterRepositoryAttribute"), t => t ); // 注册映射表为单例 services.AddSingleton(printerRepoMap); // 批量注册所有打印机仓库实现 foreach (var repoType in printerRepoMap.Values) { services.AddTransient(repoType); } // 注册Resolver services.AddSingleton<IPrinterRepositoryResolver, PrinterRepositoryResolver>();
步骤4:简化Resolver实现
现在Resolver只需要从映射表里取类型,再从DI容器获取实例,完全不用switch:
public class PrinterRepositoryResolver : IPrinterRepositoryResolver { private readonly IServiceProvider _serviceProvider; private readonly Dictionary<string, Type> _printerRepoMap; public PrinterRepositoryResolver(IServiceProvider serviceProvider, Dictionary<string, Type> printerRepoMap) { _serviceProvider = serviceProvider; _printerRepoMap = printerRepoMap; } public IPrinterRepository GetRepository(string key) { if (!_printerRepoMap.TryGetValue(key, out var repoType)) { throw new KeyNotFoundException("Service not implemented or not supported anymore!"); } return (IPrinterRepository)_serviceProvider.GetService(repoType); } }
这个方案的优势:
- 符合开闭原则:新增打印机实现只需要加类、打特性,完全不用修改现有代码
- 类型安全:编译时就能检查特性是否正确,不会出现全类名写错的问题
- 彻底解放手动维护的麻烦,适合需要频繁扩展的场景
方案2:DI工厂模式(适合实现类较少、变动不频繁的场景)
如果不想用反射和特性,也可以把映射逻辑移到DI注册环节,让Resolver通过工厂函数获取实例,代码更简洁:
步骤1:注册服务与工厂函数
在ConfigureServices里,注册所有实现类,同时注册一个Func<string, IPrinterRepository>工厂:
// 先注册所有实现类 services.AddTransient<EpsonRepository>(); services.AddTransient<HPRepository>(); // 注册工厂函数,把编码和实例创建逻辑绑定 services.AddSingleton<Func<string, IPrinterRepository>>(sp => key => { return key switch { PrinterCode.Epson => sp.GetService<EpsonRepository>(), PrinterCode.HP => sp.GetService<HPRepository>(), _ => throw new KeyNotFoundException("Service not implemented or not supported anymore!") }; }); // 注册Resolver services.AddSingleton<IPrinterRepositoryResolver, PrinterRepositoryResolver>();
步骤2:简化Resolver
现在Resolver只需要注入这个工厂函数,直接调用即可:
public class PrinterRepositoryResolver : IPrinterRepositoryResolver { private readonly Func<string, IPrinterRepository> _repoFactory; public PrinterRepositoryResolver(Func<string, IPrinterRepository> repoFactory) { _repoFactory = repoFactory; } public IPrinterRepository GetRepository(string key) { return _repoFactory(key); } }
这个方案的优势:
- 无需反射,代码更直观
- 把映射逻辑集中在DI配置环节,Resolver职责更单一
- 适合实现类不多、很少变动的场景
为什么不推荐V2的全类名方案?
你觉得这个方案不够优雅是对的,它有几个硬伤:
- 耦合性极强:数据库直接绑定代码的内部结构,类名/命名空间一变,所有数据库记录都要更新,风险极大
- 无编译时检查:全类名写错只有运行时才会报错,调试麻烦
- 暴露内部细节:把代码实现细节暴露到持久化层,违反封装原则
总结
- 如果你的打印机实现需要频繁新增,选方案1,彻底告别手动维护
- 如果实现类很少变动,选方案2,代码简洁直观
- 尽量避免V2的全类名方案,后续维护成本太高
内容的提问来源于stack exchange,提问作者Dino
相关产品推荐
相关产品推荐

