遵循开闭原则重构Invoice代码,如何免if判断实例化对应发票类?
实现方案
推荐以下两种符合开闭原则的实现方式:
方案1:轻量字典映射工厂(无第三方依赖)
通过字典预先建立发票类型字符串和对应实例创建逻辑的映射,运行时直接通过key匹配对应实现,无需编写分支判断:
// 发票工厂类 public static class InvoiceFactory { // 不区分大小写的映射字典,Value为实例创建委托 private static readonly Dictionary<string, Func<IInvoice>> _invoiceCreatorMap = new(StringComparer.OrdinalIgnoreCase) { { "invoice", () => new Invoice() }, { "discountinvoice", () => new DiscountInvoice() } }; public static IInvoice Create(string invoiceType) { if (_invoiceCreatorMap.TryGetValue(invoiceType, out var creator)) { return creator(); } throw new NotSupportedException($"未适配的发票类型:{invoiceType}"); } }
调用方式:
static void Main(string[] args) { // 模拟从API获取的模型 InvoiceModel invoiceModel = GetInvoiceFromApi(); // 直接通过工厂获取对应实例,无分支判断 IInvoice invoice = InvoiceFactory.Create(invoiceModel.Type); // 执行业务逻辑 var calculateResult = invoice.Calculate(invoiceModel.Amount); }
后续新增发票类型时,只需要新增IInvoice的实现类,再在字典中新增一条映射记录即可,无需修改原有业务逻辑。
方案2:反射自动发现(适合类型迭代频繁的场景)
如果项目中发票类型迭代频繁,可通过特性标记+反射扫描的方式完全省略手动维护映射的步骤:
- 首先定义类型标记特性
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class InvoiceTypeAttribute : Attribute { public string TypeCode { get; } public InvoiceTypeAttribute(string typeCode) => TypeCode = typeCode; }
- 为所有发票实现类添加特性标记
[InvoiceType("invoice")] public class Invoice : IInvoice { public double Calculate(double amount) => amount + 10; } [InvoiceType("discountinvoice")] public class DiscountInvoice : IInvoice { public double Calculate(double amount) => amount - 10; }
- 程序启动时扫描所有实现自动构建映射
public static class InvoiceFactory { private static readonly Dictionary<string, Type> _invoiceTypeMap; static InvoiceFactory() { _invoiceTypeMap = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => t.IsAssignableTo(typeof(IInvoice)) && !t.IsInterface && !t.IsAbstract) .Select(t => new { Type = t, Attr = t.GetCustomAttribute<InvoiceTypeAttribute>() }) .Where(t => t.Attr != null) .ToDictionary(t => t.Attr.TypeCode, t => t.Type, StringComparer.OrdinalIgnoreCase); } public static IInvoice Create(string invoiceType) { if (_invoiceTypeMap.TryGetValue(invoiceType, out var type)) { return (IInvoice)Activator.CreateInstance(type); } throw new NotSupportedException($"未适配的发票类型:{invoiceType}"); } }
该方案下后续新增发票类型只需要新增实现类并添加InvoiceType特性即可,不需要修改工厂的任何代码,完全符合开闭原则。
补充说明
如果实例不需要每次新建,可将字典的Value改为单例实例,减少重复创建对象的性能开销;如果项目使用了依赖注入容器,也可将实例的创建逻辑交由容器管理,直接从容器中解析对应类型的实例即可。
内容的提问来源于stack exchange,提问作者user12073359
相关产品推荐
相关产品推荐

