Как решить “Рост Если Оператор” проблема?

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

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 просто добавить новую реализацию.

19
задан Achilles 8 June 2010 в 14:00
поделиться

11 ответов

Если вы используете .NET, вы можете вместо этого построить его с помощью отражения. Например, если вы создавали систему плагинов, у вас была бы папка для размещения библиотек DLL плагинов. Затем ваша фабрика будет проверять доступные библиотеки 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.
                }
            }
        }
    }

Я уверен, что есть другие примеры того, как чтобы сделать это там, но если вам нужны дополнительные указатели, дайте мне знать. Ключ в том, что у всех ваших воркеров должен быть общий родительский элемент или интерфейс, чтобы вы могли вызывать их одинаково. (То есть всем вашим воркерам нужен общий метод Execute или что-то, что может вызываться из фабрики или где бы вы ни использовали объект.

2
ответ дан 30 November 2019 в 04:44
поделиться

Тип шаблона, который подходит для вышеприведенного решения, будет 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();
        }
    }
}

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

5
ответ дан 30 November 2019 в 04:44
поделиться

Если вы можете определить объект с методом 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;
}

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

1
ответ дан 30 November 2019 в 04:44
поделиться

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

   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
}
0
ответ дан 30 November 2019 в 04:44
поделиться

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

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
ответ дан 30 November 2019 в 04:44
поделиться

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

0
ответ дан 30 November 2019 в 04:44
поделиться

Я думаю, многое зависит от того, насколько предсказуемы ваши "условия". Ваш "растущий IF" по сути является фабрикой, и, возможно, рефакторинг его в собственный метод или класс поможет, но это все равно может быть растущим IF. Если ваши условия - это то, что вы не можете предсказать, например, "если Джо.горит" или "если x==2" или "если !шаттл.запущен", то вы застряли с IF'ами.

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

Просто идея

0
ответ дан 30 November 2019 в 04:44
поделиться

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

7
ответ дан 30 November 2019 в 04:44
поделиться

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

извините за псевдокод, но мой 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
ответ дан 30 November 2019 в 04:44
поделиться

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

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

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

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

Есть, наверное, еще более красивые способы

Мои 2 цента

5
ответ дан 30 November 2019 в 04:44
поделиться

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

Если вас интересует только удобочитаемость, вы можете использовать тернарный оператор или, если оценки критериев равны ==, вы можете использовать оператор switch.

0
ответ дан 30 November 2019 в 04:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: