Улучшения кода для реализации бизнес-логики - PullRequest
4 голосов
/ 28 января 2010

Я задавал этот вопрос ранее о SO. Это связано с этим. У нас есть кодовая база, подобная этой:

IRecipie FindRecipiesYouCanMake(IEnumerable<Ingredientes> stuff, Cook cook)
{
 if(stuff.Any(s=>s.Eggs && s.Flour) && cook.DinerCook)
 {
  if(s=>s.Sugar)
   return new Pancake("Yum");
  if(s=>s.Salt)
   return new Omlette("Yay");
 }
 /*.....
 ......
 .....
 loads of ifs and buts and else*/
}

Я хочу избавиться от этого беспорядка и взять более структуру данных и маршрут OO. Даже приведенный мной пример кода не так ужасен, как есть. Я посмотрел на образец спецификации и нашел его применимым. Есть идеи как улучшить код.

РЕДАКТИРОВАТЬ: Теперь, когда я это понимаю, я мог бы даже реализовать метод этой подписи:

List<IRecipe> WhatAllCanBeCooked(IEnumerable<Ingredients> stuff, Cook cook);

Ответы [ 4 ]

3 голосов
/ 29 января 2010

Я бы делегировал эту логику отдельным классам IRecipie:

if (Pancake.CanBeMadeBy(stuff, cook)) {
    return new Pancake("Yum");
}
....


public class Pancake: IRecipe {
    ...
    public static bool CanBeMadeBy(IEnumerable<Ingredientes> stuff, Cook cook) {
        return stuff.Any(s=>s.Eggs && s.Flour && s.Sugar) && cook.DinerCook;
    }

}

Редактировать в ответ на комментарий

Чтобы найти все рецепты, которые можно приготовить,просто сделайте что-то вроде этого:

List<IRecipe> results = new List<IRecipe>();

if (Pancake.CanBeMadeBy(stuff, cook)) {
    results.Add(new Pancake("Yum");
}
....

Редактировать 2 В качестве альтернативы, если вы где-то храните список всех возможных рецептов, вы можете превратить CanBeMadeBy в метод экземпляра вместо статическогои сделайте это:

List<IRecipe> allRecipes = // all possible recipes
...
return allRecipes.Where(r => r.CanBeMadeBy(stuff, cook));
1 голос
/ 29 января 2010

Некоторые идеи:

  • использование таблицы принятия решений

  • используйте шаблон стратегии . Это помогает вам инкапсулировать группу действий или параметров, относящихся друг к другу в разных конкретных классах. После того, как вы решили, какую стратегию использовать, вам больше не нужно никаких «если» для распределения между стратегиями.

РЕДАКТИРОВАТЬ: некоторые дополнительные идеи:

  • начинать с «малого»: чаще всего простой рефакторинг для более мелких, хорошо именуемых, многократно используемых функций поможет вам уменьшить if-else-if-else-soup. Иногда, простая, хорошо названная логическая переменная делает свое дело. Оба примера для рефакторингов вы найдете в книге Фаулера «Рефакторинг» .

  • думайте «большой»: если у вас действительно много сложных бизнес-правил, создание «языка, специфичного для предметной области» - это вариант, который иногда может быть правильным способом снизить сложность. Вы найдете много материала на эту тему, просто погуглив для этого. Цитируя Дэвида Уилера Все проблемы в информатике могут быть решены с помощью другого уровня косвенности .

1 голос
/ 29 января 2010

ОРИГИНАЛЬНЫЙ ПОЧТА - Мартин Фаулер решил эту проблему для вас ... она называется шаблоном спецификации.
http://en.wikipedia.org/wiki/Specification_pattern

ОБНОВЛЕННЫЙ ПОСТ -

Рекомендуется использовать шаблон составной спецификации, когда:

  • Вам нужно выбрать подмножество объектов на основе некоторых критериев,
  • Вам нужно проверить, что для определенной роли используются только подходящие объекты, или
  • Вам необходимо описать, что может делать объект, без объяснения деталей того, как этот объект делает

Истинная сила паттерна заключается в способности комбинировать различные спецификации в композиты с отношениями AND, OR и NOT. Объединение различных спецификаций может быть выполнено во время разработки или выполнения.

Книга Эрика Эвана о доменно-управляемом дизайне имеет прекрасный пример этого паттерна (Манифест доставки)

Это ссылка на вики:

http://en.wikipedia.org/wiki/Specification_pattern

В нижней части вики-ссылки находится эта PDF-ссылка:

http://martinfowler.com/apsupp/spec.pdf

0 голосов
/ 29 января 2010

Я думаю, что этот блок кода, по сути, пытается выполнить, связывая рецепты с ингредиентами внутри этого рецепта. Один из подходов состоит в том, чтобы включить список ингредиентов в сам класс рецепта, а затем сравнить его со списком переданных ингредиентов, например:

public interface IRecipe {
   IEnumerable<Ingredient> Ingredients { get; }
}

public class Omlette : IRecipe {
   public IEnumerable<Ingredient> Ingredients { 
      get {
         return new Ingredient[]{new Ingredient("Salt"), new Ingredient("Egg")};
      }
   }
}

// etc. for your other classes.

IRecipie FindRecipiesYouCanMake(IEnumerable<Ingredientes> stuff, Cook cook)
{
    var query = Recipes.Where(r => !r.Ingredients.Except(stuff).Any());
    return query.First();
}

Предполагается, что у вас есть коллекция всех ваших рецептов. Это должно быть достаточно просто, чтобы настроить статический список или получить из базы данных.

Запрос Linq ищет любые рецепты, в которых все ингредиенты, переданные в материале, присутствуют в списке ингредиентов (или, как указано, в ингредиентах нет ингредиентов, которых нет в материале). Это также может уменьшить необходимость иметь подклассы для рецептов, что кажется немного странным (хотя, насколько я знаю, есть и другие причины, по которым вам это понадобится)

...