基于SOLID开闭原则:如何无条件语句初始化不同类?
如何在不使用条件语句的情况下初始化不同类(遵循开闭原则)
这确实是个非常典型的困惑——很多开闭原则的讲解会告诉你要“对扩展开放、对修改关闭”,但实操中怎么绕开恼人的if/switch来创建不同的类实例呢?结合你给出的代码片段,我来分享几个实用且符合SOLID原则的方案:
首先先补全你的基础代码,方便后续演示:
public enum PreferredMeal { Vegetarian = 1, NonVegetarian = 2 } public class Customer { public string Name { get; set; } public PreferredMeal PreferredMeal { get; set; } } public class Meal { public string Name { get; set; } // 可以添加其他餐食属性,比如卡路里、食材等 } public interface IMealGenerator { List<Meal> GenerateMeals(); } // 素食餐食生成器 public class VegetarianMealGenerator : IMealGenerator { public List<Meal> GenerateMeals() { return new List<Meal> { new Meal { Name = "希腊沙拉" }, new Meal { Name = "蘑菇素食意面" } }; } } // 非素食餐食生成器 public class NonVegetarianMealGenerator : IMealGenerator { public List<Meal> GenerateMeals() { return new List<Meal> { new Meal { Name = "西冷牛排" }, new Meal { Name = "香烤柠檬鸡" } }; } }
方案1:使用字典映射(注册表模式)
核心思路是提前把枚举值和对应的类实例工厂关联起来,创建实例时直接从字典中查找,完全不需要条件判断:
public static class MealGeneratorFactory { // 静态字典存储枚举到生成器工厂的映射 private static readonly Dictionary<PreferredMeal, Func<IMealGenerator>> _generatorRegistry = new() { { PreferredMeal.Vegetarian, () => new VegetarianMealGenerator() }, { PreferredMeal.NonVegetarian, () => new NonVegetarianMealGenerator() } }; public static IMealGenerator GetGenerator(PreferredMeal mealType) { if (_generatorRegistry.TryGetValue(mealType, out var generatorFactory)) { return generatorFactory(); } throw new ArgumentOutOfRangeException(nameof(mealType), $"不支持的餐食类型:{mealType}"); } } // 使用示例 var customer = new Customer { Name = "Alice", PreferredMeal = PreferredMeal.Vegetarian }; var mealGenerator = MealGeneratorFactory.GetGenerator(customer.PreferredMeal); var recommendedMeals = mealGenerator.GenerateMeals();
为什么符合开闭原则?
当你需要新增一种餐食类型(比如Vegan纯素食)时,只需要:
- 新增一个
VeganMealGenerator : IMealGenerator的实现类 - 在
_generatorRegistry字典中添加一行映射关系
完全不需要修改GetGenerator方法的逻辑,真正做到了扩展开放、修改关闭。
方案2:利用依赖注入容器(适合大型项目)
如果你的项目已经使用了依赖注入(比如.NET的Microsoft.Extensions.DependencyInjection),可以利用键控服务(.NET 6+支持)来实现无条件判断的实例创建:
// 注册服务(通常在程序启动时的配置阶段) var services = new ServiceCollection(); // 注册具体的生成器实现 services.AddTransient<VegetarianMealGenerator>(); services.AddTransient<NonVegetarianMealGenerator>(); // 用枚举作为键,注册IMealGenerator的映射 services.AddKeyedTransient<IMealGenerator, VegetarianMealGenerator>(PreferredMeal.Vegetarian); services.AddKeyedTransient<IMealGenerator, NonVegetarianMealGenerator>(PreferredMeal.NonVegetarian); // 构建服务提供器 var serviceProvider = services.BuildServiceProvider(); // 使用示例 var customer = new Customer { Name = "Bob", PreferredMeal = PreferredMeal.NonVegetarian }; var mealGenerator = serviceProvider.GetKeyedService<IMealGenerator>(customer.PreferredMeal); var recommendedMeals = mealGenerator.GenerateMeals();
优势:
- 把实例创建的控制权交给DI容器,简化了工厂类的维护
- 新增餐食类型时,只需要注册新的键控服务,原有的业务逻辑完全不用改动
- 天然支持依赖注入(比如如果你的生成器需要依赖其他服务,DI容器会自动注入)
进阶方案:反射自动扫描注册(极致符合开闭原则)
如果想彻底避免手动添加映射,可以用反射自动扫描程序集中所有IMealGenerator的实现类,并通过特性标记来关联枚举值:
首先定义一个特性:
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class MealTypeAttribute : Attribute { public PreferredMeal MealType { get; } public MealTypeAttribute(PreferredMeal mealType) { MealType = mealType; } }
然后给生成器类添加特性:
[MealType(PreferredMeal.Vegetarian)] public class VegetarianMealGenerator : IMealGenerator { /* 实现代码 */ } [MealType(PreferredMeal.NonVegetarian)] public class NonVegetarianMealGenerator : IMealGenerator { /* 实现代码 */ }
最后在工厂类中用反射初始化字典:
public static class MealGeneratorFactory { private static readonly Dictionary<PreferredMeal, Func<IMealGenerator>> _generatorRegistry; static MealGeneratorFactory() { _generatorRegistry = InitializeRegistry(); } private static Dictionary<PreferredMeal, Func<IMealGenerator>> InitializeRegistry() { var registry = new Dictionary<PreferredMeal, Func<IMealGenerator>>(); // 扫描当前程序集中所有IMealGenerator的非抽象实现类 var generatorTypes = typeof(IMealGenerator).Assembly.GetTypes() .Where(t => typeof(IMealGenerator).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); foreach (var type in generatorTypes) { // 获取类上的MealType特性 var mealTypeAttr = type.GetCustomAttribute<MealTypeAttribute>(); if (mealTypeAttr != null) { // 添加到注册表:用Activator创建实例 registry[mealTypeAttr.MealType] = () => (IMealGenerator)Activator.CreateInstance(type); } } return registry; } public static IMealGenerator GetGenerator(PreferredMeal mealType) { if (_generatorRegistry.TryGetValue(mealType, out var generatorFactory)) { return generatorFactory(); } throw new ArgumentOutOfRangeException(nameof(mealType), $"不支持的餐食类型:{mealType}"); } }
极致优势:
现在新增餐食类型时,只需要创建一个带有[MealType]特性的IMealGenerator实现类,其他任何代码都不需要修改——完全遵循开闭原则的核心思想。
内容的提问来源于stack exchange,提问作者muhihsan
相关产品推荐
相关产品推荐

