利用开闭原则 (SOLID)

Utilizing Open Closed Principle (SOLID)

我看过 SOLID Open Close Principle 的几个示例。这些解释通常很清楚。

但是我还有1个疑问,那就是我们如何在不使用条件语句的情况下初始化那些不同的classes?

示例代码如下:

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 语句来识别哪个 class 实现了 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 方法来做到这一点? :)

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

how do we initialize those different classes without using the conditional statement?

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

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

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

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

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