代码之家  ›  专栏  ›  技术社区  ›  Achilles

如何解决日益增长的If语句问题?

  •  19
  • Achilles  · 技术社区  · 15 年前

    我一直在读一些关于设计模式的书,想要一些视角。考虑以下几点:

    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 回复  |  直到 15 年前
        1
  •  2
  •   Dave Sanders    15 年前

    如果您使用的是.NET,则可以使用反射来构建它。例如,如果您正在创建一个插件系统,那么您将有一个文件夹将插件DLL放入其中。然后您的工厂将查看可用的dll,检查每个dll是否有适当的反射属性,然后将这些属性与传入的任何字符串进行匹配,以决定选择和调用哪个对象。

    这使得你不必重新编译你的主应用程序,尽管你必须在其他dll中构建你的工人,然后有办法告诉你的工厂使用哪一个。

    下面是一些非常快速和肮脏的伪代码,让大家明白这一点:

    假设您有一个名为Workers.DLL的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 {}
    

    然后在应用程序的worker 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.
                    }
                }
            }
        }
    

        2
  •  7
  •   Bill the Lizard    15 年前

    Factory method pattern . (请参见下面的ImageReaderFactory示例 Encapsulation .)

        3
  •  5
  •   James    15 年前

    适合上述解决方案的模式类型是 Factory Pattern . 在这种情况下,您不需要知道所需对象的具体类型,它只需要实现 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();
            }
        }
    }
    

    因此,在您的例子中,您可以有一个小的helper方法,它可以根据条件计算出所需的正确类型的Worker。这甚至可以封装在只读属性中,您只需将其传递到工厂。

        4
  •  5
  •   Mike Gleason jr Couturier    15 年前

    您可以为每个对象类型创建工厂,这些工厂可以有一个函数,该函数将criterias作为参数,并在参数满足时返回一个IGruntWorker(否则为null)。

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

    当需要新的条件时,只需将其添加到工厂列表中,无需修改循环。

    可能还有更美的方法

        5
  •  1
  •   Norman Ramsey    15 年前

    如果你能用 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
  •  1
  •   sylvanaar    15 年前

    你能用一种不同的访客模式吗?称之为工厂访客(也许)

    请原谅伪代码,但是我的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
  •  0
  •   David    15 年前

    我认为只要您的最有可能的条件是第一个命令,允许运行时跳转其余的情况,这是好的。

    如果您关心的只是可读性,那么您可以使用三元运算符,或者如果条件求值只是==,那么您可以使用switch语句。

        8
  •  0
  •   Clyde    15 年前

    我认为这种模式很好,只要您的条件和操作是单行/方法调用。这很容易阅读,并且准确地反映了您的逻辑:

       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
  •  0
  •   n8wrl    15 年前

    这些uberif的一个缺点是它们可能对您的应用程序有影响。也就是说,你需要打电话/触摸/检查什么来确定哪个“如果”应该是真的?你可能会有成吨的全球原油或大量的参数传递给你的'工厂'。我不久前做的一件事就是实现一个包含布尔委托(Func)和类型数组的排序工厂。我会在初始化时注册布尔委托和类型,并遍历工厂中的列表调用每个委托,直到得到一个“true”,然后实例化该类型。这对我来说效果很好,因为我能够在不编辑工厂的情况下“注册”新的条件。

        10
  •  0
  •   jeff    15 年前

    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
  •  0
  •   Codism    15 年前

    您可以使用反射来查找给定类型的构造函数,并由该构造函数创建实例。当然,构造器必须遵循一定的模式。在上面的示例中,所有都是默认构造函数。