Использование открытого закрытого принципа (ТВЕРДЫЙ) - PullRequest
0 голосов
/ 28 апреля 2018

Я видел пару примеров по принципу SOLID Open Close. И эти объяснения обычно довольно ясны.

Но у меня все еще есть еще один вопрос: как мы можем инициализировать эти разные классы без использования условного оператора?

Вот пример кода:

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, похоже, не удовлетворяет принципу открытого закрытия. Есть ли лучший твердый способ сделать это? :)

Ответы [ 2 ]

0 голосов
/ 28 апреля 2018

как мы инициализируем эти разные классы без использования условного оператора?

Условное утверждение не является злом. Это необходимо, когда нам нужно сопоставить некоторые условия (PreferredMeal в вашем примере) с соответствующими реализациями (интерфейс IMealGenerator), как и оператор switch.

Проблема в вашем коде в том, что вы создаете реализации IMealGenerator в методе, в котором он будет использоваться. Это неверно, потому что в большинстве случаев у вас будет несколько методов, таких как GenerateCustomerMeals. Эти методы не должны знать, как сопоставить PreferredMeal с реализацией IMealGenerator. Единственный класс знает, что сопоставление - это MealGeneratorFactory, например:

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

И все ваши методы, такие как GenerateCustomerMeals, зависят от IMealGeneratorFactory, получают IMealGenerator и используют его.

Внедрение зависимостей облегчит задачу, но выводы те же.

0 голосов
/ 28 апреля 2018

Если у вас есть несколько реализаций, и вам нужно переключаться между ними, одним из вариантов является предоставление дополнительной реализации, которая позволяет переключаться между ними. Таким образом, 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 , которая поддерживает выбор реализации на основе ключей .

Это может позволить регистрировать сервисы отдельно для каждого ключа, а затем соглашение о поиске сервисов, например:

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