利用开闭原则 (SOLID)

Posted

技术标签:

【中文标题】利用开闭原则 (SOLID)【英文标题】:Utilizing Open Closed Principle (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 方法来做到这一点? :)

【问题讨论】:

【参考方案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);
   

【讨论】:

以上是关于利用开闭原则 (SOLID)的主要内容,如果未能解决你的问题,请参考以下文章

SOLID - 违反开闭原则

使用接口实现开闭原则(SOLID)

如何应用 SOLID 原则在 React 中整理代码之开闭原则

如何应用 SOLID 原则在 React 中整理代码之开闭原则

设计模式SOLID - 开闭原则

开放封闭原则|SOLID as a rock