利用开放闭合原理(SOLID)

时间:2018-04-28 09:16:44

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

我见过几个SOLID Open Close Principle的样本。这些解释通常很清楚。

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

以下是示例代码:

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方法来做到这一点? :)

2 个答案:

答案 0 :(得分:2)

  

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

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

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

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

GenerateCustomerMeals等所有方法都取决于IMealGeneratorFactory,获得IMealGenerator并使用它。

依赖注入会使事情变得更容易,但结论是一样的。

答案 1 :(得分:1)

如果您有多个实现,并且需要在它们之间切换,一个选项是提供允许您在它们之间切换的其他实现。这样,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);
   }
}

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

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

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);
   }
}