Как решить проблему «Growing If Statement»? - PullRequest
19 голосов
/ 08 июня 2010

Я немного почитал о шаблонах проектирования и хотел взглянуть на них. Учтите следующее:

Dim objGruntWorker as IGruntWorker

if SomeCriteria then
   objGruntWorker = new GoFor()
else if SomeOtherCriteria then
   objGruntWorker = new Newb()
else if SomeCriteriaAndTheKitchenSink then
   objGruntWorker = new CubeRat()
end if

objGruntWorker.GetBreakfast()
system.threading.thread.sleep(GetMilliSecondsFromHours(4))
objGruntWorker.GetLunch()

Приведенный выше код увеличивается каждый раз, когда возникает новый критерий. Я видел такой код повсюду, и по незнанию написал его сам. Как это должно быть решено? У этого вида анти-паттерна есть более «формальное» имя? Спасибо за вашу помощь!

Редактировать: Еще одно соображение - я хочу избежать перекомпиляции существующих реализаций IGruntWorker, просто чтобы добавить новую реализацию.

Ответы [ 11 ]

7 голосов
/ 08 июня 2010

Такая логика часто инкапсулируется с использованием шаблона фабричного метода . (См. Пример ImageReaderFactory в разделе Encapsulation .)

5 голосов
/ 08 июня 2010

Вы можете создать фабрики для каждого типа объекта, и эти фабрики могут иметь функцию, которая принимает критерии в качестве параметра и возвращает IGruntWorker, если параметры удовлетворяются (или в противном случае - ноль).

Затем вы можете создать список этих фабрик и просмотреть их, как (извините, я парень на C #):

Dim o as IGruntWorker;
foreach (IGruntWorkerFactory f in factories)
{
    o = f.Create(criterias);
    if (o != null)
        break;
}

Когда требуется новый критерий, вы только добавляете его в список фабрик, не нужно изменять цикл.

Возможно, есть еще несколько прекрасных способов

Мои 2 цента

5 голосов
/ 08 июня 2010

Тип шаблона, который будет соответствовать вышеуказанному решению, будет Factory Pattern . У вас есть ситуация, когда вам не нужно знать конкретный тип объекта, который вам нужен, он просто должен реализовать IGruntWorker. Таким образом, вы создаете фабрику, которая принимает критерии и на основе этих критериев вы бы возвращали конкретный объект IGruntWorker. Обычно хорошей идеей является сопоставление критериев некоторому идентификатору, то есть перечислению или константе для удобочитаемости, например

public enum WorkerType
{
    Newbie,
    Average,
    Expert
}

public class WorkerFactory
{
    public static IGruntWorker GetWorker(WorkerType type)
    {
        switch (type)
        {
            case WorkerType.Newbie:
                 return new NewbieWorker();
            case WorkerType.Average:
                 return new AverageWorker();
            case WorkerType.Expert:
                 return new ExpertWorker();
        }
    }
}

Таким образом, в вашем случае у вас может быть небольшой вспомогательный метод, который определяет правильный тип работника, требуемый на основе критериев. Это может быть даже заключено в свойство только для чтения, которое вы просто передаете фабрике.

2 голосов
/ 08 июня 2010

Если вы используете .NET, вы можете построить его с отражением. Например, если вы создаете систему плагинов, у вас будет папка, в которую можно вставлять библиотеки плагинов. Затем ваша фабрика будет просматривать доступные библиотеки DLL, проверять каждую из них на наличие соответствующих атрибутов отражения, а затем сопоставлять эти атрибуты с любой строкой, которая была передана, чтобы решить, какой объект выбрать и вызвать.

Это избавляет вас от необходимости перекомпилировать основное приложение, хотя вам придется собирать своих работников в других библиотеках DLL, а затем иметь способ сообщить своей фабрике, какое из них использовать.

Вот несколько действительно быстрых и грязных псевдокодов, чтобы понять суть:

Предполагается, что у вас есть сборка DLL под названием Workers.DLL

Установите атрибут WorkerTypeAttribute со строковым свойством Name и конструктором, чтобы можно было установить это свойство Name.

[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class WorkerTypeAttribute : Attribute
{
    string _name;
    public string Name { get { return _name; } }
    public WorkerTypeAttribute(string Name)
    {
        _name = Name;
    }
}

Затем вы примените этот атрибут к любому рабочему классу, который вы определили как:

[WorkerType("CogWorker")]
public class CogWorker : WorkerBase {}

Тогда на фабрике рабочих вашего приложения вы напишете код вроде:

 public void WorkerFactory(string WorkerType)
    {
        Assembly workers = Assembly.LoadFile("Workers.dll");
        foreach (Type wt in workers.GetTypes())
        { 
            WorkerTypeAttribute[] was = (WorkerTypeAttribute[])wt.GetCustomAttributes(typeof(WorkerTypeAttribute), true);
            if (was.Count() == 1)
            {
                if (was[0].Name == WorkerType)
                { 
                    // Invoke the worker and do whatever to it here.
                }
            }
        }
    }

Я уверен, что есть и другие примеры того, как это сделать, но если вам нужны дополнительные указатели, дайте мне знать. Главное, чтобы у всех ваших работников был общий родитель или интерфейс, чтобы вы могли вызывать их одинаково. (Т. Е. Всем вашим работникам нужен общий метод «Выполнить» или что-то, что может быть вызвано на заводе или везде, где вы используете объект.

1 голос
/ 08 июня 2010

Не могли бы вы использовать вместо этого вариант шаблона посетителя? Назовите это фабричным посетителем (возможно)

извините за псевдокод, но мой VB ржавый

Dim objGruntWorker as IGruntWorker

objGruntWorker = null

// all your objects implement IFactoryVisitor
Dim factory as IFactoryVisitor
while objGruntWorker == null
    factory = factoryCollection.GetNext 
    objGruntWorker = factory.TryBuild(...)
end

objGruntWorker.GetBreakfast()
system.threading.thread.sleep(GetMilliSecondsFromHours(4))
objGruntWorker.GetLunch()
1 голос
/ 08 июня 2010

Если вы можете определить объект с помощью метода checkCriteria, тогда вы можете сделать этот код управляемым таблицей. Я не знаю C #, так что не забывайте о синтаксисе:

public class WorkerFactory {
    IGruntWorker makeWorkerIfCriteria(criteria_parameters parms);
}

extern WorkerFactory worker_factories[];  /* table with factories in order */

IGruntWorker makeJustTheRightWorker(criteria_parameters actual_critera) {
  for (i = 0; i < worker_factories.length(); i++) {
    IGruntWorwer w = worker_factories[i].makeWorker(actual_criteria);
    if (!null(w)) return w;
  }
  --- grim error --- /* table not initiailized correctly */
}

Тогда некоторые объекты в таблице выглядят так:

public class MakeGoFor(critera_parameters cp) {
   if SomeCriteria then
      return new GoFor();
   else
      return NULL;
}

Вы можете перекомпилировать таблицу в отдельный модуль без необходимости перекомпиляции кода выбора. На самом деле, если вы амбициозны, вы можете даже построить таблицу во время выполнения на основе аргументов командной строки или содержимого файла ...

0 голосов
/ 08 июня 2010

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

0 голосов
/ 08 июня 2010

Я знаю ваш .NET, но именно так я делаю нечто подобное в веб-приложении на Java, где мои «if-thens» растут…. Все еще требует перекомпиляции, но легко добавлять другие действия или в вашем случае хрюкать рабочие.

private HashMap actionMap = new HashMap();

actionMap.put("cubeRat", new CubeRatAction());
actionMap.put("newb", new NewbAction());
actionMap.put("goFor", new goForAction());
actionMap.put("other", new otherAction());

String op = request.getParameter("criteria");  // not sure how your criteria is passed in but this is through a parameter in my URL.
ControllerAction action = (ControllerAction) actionMap.get(op);
if (action != null) {
     action.GetBreakfast();
     action.Sleep();
     action.GetLunch();              
} else {
     String url = "views/errorMessage_v.jsp";
     String errMessage = "Operation '" + op + "' not valid for in '" + request.getServletPath() + "' !!";
     request.setAttribute("message", errMessage);
     request.getRequestDispatcher(url).forward(request, response);
}
0 голосов
/ 08 июня 2010

Я думаю, что многое зависит от того, насколько предсказуемы ваши «условия». Ваше «растущее ЕСЛИ» по сути является фабрикой, и, возможно, реорганизация его в свой собственный метод или класс может помочь, но, возможно, она все еще будет «растущим ЕСЛИ». Если ваши условия являются вещами, которые вы не можете предсказать, например, «если joe.is.on.fire» или «если x == 2» или «если! Shuttle.is.launched», то вы застряли с IF.

Одна плохая вещь в этих uber-IF - это область, которую они могут иметь над вашим приложением. То есть все, что вам нужно, чтобы позвонить / коснуться / проверить, чтобы определить, какое «если» должно быть правдой? Вы можете получить тонны глобальных фреймов или множество параметров для передачи на «фабрику». Одна вещь, которую я сделал недавно, чтобы помочь с этим, заключалась в реализации фабрики сортов, которая содержала массив логических делегатов (Func) и типов. Я бы регистрировал логические делегаты и типы во время инициализации и перебирал список в фабрике, вызывая каждый делегат, пока не получил «истину», а затем не создал экземпляр этого типа. Это помогло мне, потому что я смог «зарегистрировать» новые условия без редактирования фабрики.

Просто идея

0 голосов
/ 08 июня 2010

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

   if (ConditionOne())
   {
     BuildTheWidget();
   }
   else if (ConditionTwo())
   {
     RaiseTheAlarm();
   }
   else if (ConditionThree())
   {
      EverybodyGetsARaise();
   }

Даже если существует 20 различных условий, это, вероятно, является точным отражением некоторой сложной бизнес-логики вашего приложения.

С другой стороны, это катастрофа для удобства чтения

if (  ((A && B) || C &&
      (D == F) || (F == A)))
{
   AA;
   BB;
   //200 lines of code
}
else if ( (A || D) && B)
{
  // 200 more lines
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...