如何基于枚举参数创建实例?规避开闭原则违反的方案探讨
符合开闭原则的水果实例创建方案
你当前的简单工厂实现确实违反了开闭原则——每次新增水果类型都要修改工厂类的switch逻辑。下面提供几种更优的解决方案,都能做到新增水果类型时无需修改工厂核心代码:
方案一:特性+反射自动映射
通过自定义特性关联枚举与水果类,初始化时扫描所有水果类建立映射,后续新增水果只需给类添加特性即可。
步骤1:定义关联特性
[AttributeUsage(AttributeTargets.Class)] public class FruitTypeAttribute : Attribute { public Fruits FruitType { get; } public FruitTypeAttribute(Fruits fruitType) { FruitType = fruitType; } }
步骤2:给水果类标记特性
[FruitType(Fruits.Apple)] public class Apple : Fruit { } [FruitType(Fruits.Orange)] public class Orange : Fruit { } [FruitType(Fruits.Banana)] public class Banana : Fruit { }
步骤3:实现工厂类
public class FruitFactory { private static readonly Dictionary<Fruits, Type> _fruitTypeMap; static FruitFactory() { // 程序启动时扫描所有继承Fruit的类,构建枚举与类型的映射表 _fruitTypeMap = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(asm => asm.GetTypes()) .Where(type => typeof(Fruit).IsAssignableFrom(type) && !type.IsAbstract) .Select(type => new { Type = type, Attr = type.GetCustomAttribute<FruitTypeAttribute>() }) .Where(x => x.Attr != null) .ToDictionary(x => x.Attr.FruitType, x => x.Type); } public Fruit CreateFruit(Fruits fruitType) { if (_fruitTypeMap.TryGetValue(fruitType, out var type)) { return (Fruit)Activator.CreateInstance(type); } throw new ArgumentOutOfRangeException(nameof(fruitType), "不支持的水果类型"); } }
优势:新增水果仅需添加类和特性,无需修改工厂代码;缺点:反射存在轻微性能开销(但初始化仅执行一次)。
方案二:注册式工厂
通过提供注册方法,允许外部将枚举与对应的实例创建逻辑绑定,完全解耦工厂与具体水果类。
工厂实现
public class FruitFactory { private readonly Dictionary<Fruits, Func<Fruit>> _creators = new(); // 注册水果创建逻辑 public void Register(Fruits fruitType, Func<Fruit> creator) { if (_creators.ContainsKey(fruitType)) { throw new InvalidOperationException($"已注册{fruitType}的创建逻辑"); } _creators[fruitType] = creator; } // 创建实例 public Fruit CreateFruit(Fruits fruitType) { if (_creators.TryGetValue(fruitType, out var creator)) { return creator(); } throw new ArgumentOutOfRangeException(nameof(fruitType), "不支持的水果类型"); } }
使用方式
// 初始化工厂并注册所有水果 var factory = new FruitFactory(); factory.Register(Fruits.Apple, () => new Apple()); factory.Register(Fruits.Orange, () => new Orange()); factory.Register(Fruits.Banana, () => new Banana()); // 创建实例 var apple = factory.CreateFruit(Fruits.Apple);
优势:无反射开销,支持自定义创建逻辑(例如注入依赖);缺点:需要手动注册所有水果类型。
方案三:结合依赖注入容器
如果项目已使用DI容器(如Autofac、Microsoft.Extensions.DependencyInjection),可直接利用容器管理实例创建,同时维护枚举与服务类型的映射。
示例(Microsoft.Extensions.DependencyInjection)
// 注册水果服务 var services = new ServiceCollection(); services.AddTransient<Apple>(); services.AddTransient<Orange>(); services.AddTransient<Banana>(); // 建立枚举与服务类型的映射 var fruitServiceMap = new Dictionary<Fruits, Type> { { Fruits.Apple, typeof(Apple) }, { Fruits.Orange, typeof(Orange) }, { Fruits.Banana, typeof(Banana) } }; var provider = services.BuildServiceProvider(); // 创建实例的方法 public Fruit CreateFruit(Fruits fruitType) { if (fruitServiceMap.TryGetValue(fruitType, out var serviceType)) { return (Fruit)provider.GetService(serviceType); } throw new ArgumentOutOfRangeException(nameof(fruitType)); }
优势:复用DI容器的生命周期管理,支持依赖注入;缺点:依赖项目的DI配置。
内容的提问来源于stack exchange,提问作者Iliya Belan
相关产品推荐
相关产品推荐

