Подтвердить что ты не робот

Как решить проблему "Growing If Statement"?

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

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

4b9b3361

Ответ 1

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

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

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

Предполагая, что у вас есть сборка 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 {}

Затем в вашем рабочем приложении factory вы должны написать код, например:

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

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

Ответ 3

Тип шаблона, который подходит для вышеупомянутого решения, будет Factory Pattern. У вас есть ситуация, когда вам не нужно знать конкретный тип объекта, который вам нужен, он просто должен реализовать IGruntWorker. Таким образом, вы создаете factory, который принимает критерии и на основе этих критериев вы возвращаете конкретный объект 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();
        }
    }
}

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

Ответ 4

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

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

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

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

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

Мои 2 цента

Ответ 5

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

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

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

Ответ 6

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

извините псевдокод, но мой 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()

Ответ 7

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

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

Ответ 8

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

   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
}

Ответ 9

Я думаю, что многое зависит от того, насколько предсказуемы ваши "условия". Ваш "растущий IF" по существу является factory, и, возможно, рефакторинг его по своему методу или классу поможет, но он может ВСЕГДА стать растущим IF. Если ваши условия - это вещи, которые вы не можете предсказать, например "если joe.is.on.fire" или "if x == 2" или "if! Shuttle.is.launched", тогда вы застреваете с IF.

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

Просто идея

Ответ 10

Я знаю ваш .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);
}

Ответ 11

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