You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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纯素食)时,只需要:

  1. 新增一个VeganMealGenerator : IMealGenerator的实现类
  2. 在_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:18:59