【问题标题】:Utilizing Open Closed Principle (SOLID)利用开闭原则 (SOLID)
【发布时间】:2018-04-28 09:16:44
【问题描述】:

我已经看过几个关于 SOLID 开闭原则的示例。这些解释通常很清楚。

但我心中还有一个问题,那就是我们如何在不使用条件语句的情况下初始化那些不同的类?

这里是示例代码:

public enum PreferredMeal
{
    Vegetarian = 1,
    NonVegetarian = 2
}

public class Customer
{
    public string Name { get; set; }
    public PreferredMeal PreferredMeal { get; set; }
}

public interface IMealGenerator
{
    List<Meal> GenerateMeals(Customer customer);
}

public class VegetarianMealGenerator : IMealGenerator
{
    public override List<Meal> GenerateMeals(Customer customer)
    {
        // Some codes here
    }
}

public class NonVegetarianMealGenerator : IMealGenerator
{
    public override List<Meal> GenerateMeals(Customer customer)
    {
        // Some codes here
    }
}

假设我得到了以下数据,我被要求读取这些数据并为所有客户生成餐点。

Input(CustomerName, PreferredMeal):

Customer1,1
Customer2,1
Customer3,2

我们不是也要使用 if 语句来根据客户来确定要实例化哪个实现 MealGenerator 的类,如下所示?

// Let's assume this function is called after all customers data has been read
// And those data is passed here
public void GenerateCustomerMeals(List<Customer> customers)
{
    foreach (var customer in customers)
    {
        if (customer.PreferredMeal == PreferredMeal.Vegetarian)
            new VegetarianMealGenerator().GenerateMeals(customer);
        else if (customer.PreferredMeal == PreferredMeal.NonVegetarian)
            new NonVegetarianMealGenerator().GenerateMeals(customer);
    }
}

如果是这样的话,那么 GenerateCustomerMeals 似乎不满足开闭原则。有没有更好的 SOLID 方法来做到这一点? :)

【问题讨论】:

    标签: c# solid-principles open-closed-principle


    【解决方案1】:

    我们如何在不使用条件语句的情况下初始化这些不同的类?

    条件语句并不邪恶。当我们需要将一些条件(在您的示例中为 PreferredMeal)映射到相应的实现(IMealGenerator 接口)时,这是必要的,switch 语句也是如此。

    您的代码中的问题是您正在使用 IMealGenerator 的方法构建实现。这是不正确的,因为在大多数情况下,您将拥有一些方法,例如GenerateCustomerMeals。这些方法不应该知道如何将PreferredMeal 映射到IMealGenerator 的实现。唯一知道映射是 MealGeneratorFactory 的类是这样的:

    class MealGeneratorFactory : IMealGeneratorFactory 
    {
        IMealGenerator GetMealGenerator(Customer customer)
        {
            // if/switch here
        }
    }
    

    而你所有的方法,比如GenerateCustomerMeals 都依赖于IMealGeneratorFactory,得到一个IMealGenerator 并使用它。

    依赖注入会让事情变得更简单,但结论是一样的。

    【讨论】:

      【解决方案2】:

      如果您有多个实现,并且需要在它们之间切换,则一种选择是提供一个额外的实现,让您可以在它们之间切换。通过这种方式,SOLID 仍然保留,因为路由机制对消费代码隐藏。

      public class RoutingMealGenerator : MealGenerator
      {
         public override List<Meal> GenerateMeals(Customer customer)
         {
            if (customer.PreferredMeal == PreferredMeal.Vegetarian)
               return new VegetarianMealGenerator().GenerateMeals(customer);
            else if (customer.PreferredMeal == PreferredMeal.NonVegetarian)
               return new NonVegetarianMealGenerator().GenerateMeals(customer);
         }
      }
      

      更好的选择是使用支持implementation selection based on keys 的依赖注入框架,例如Autofac

      这可以允许针对每个键单独注册服务,然后进行服务查找安排,例如:

      public class PreferenceRoutingMealGenerator : MealGenerator
      {
         IIndex<PreferredMeal, MealGenerator> _serviceLookup;
      
         public PreferenceRoutingMealGenerator( IIndex<PreferredMeal, MealGenerator> serviceLookup )
         {
            _serviceLookup = serviceLookup;
         }
      
         public override List<Meal> GenerateMeals(Customer customer)
         {
            MealGenerator gen = _serviceLookup[customer.PreferredMeal];
      
            return gen.GenerateMeals(customer);
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-01
        • 2010-12-03
        相关资源
        最近更新 更多