如何解决“成长如果声明”的问题?

时间:2010-06-08 13:43:35

标签: .net design-patterns

我一直在阅读有关设计模式的一些内容,并想要一些观点。请考虑以下事项:

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 个答案:

答案 0 :(得分:7)

这种逻辑通常使用Factory method pattern进行封装。 (请参阅Encapsulation下的ImageReaderFactory示例。)

答案 1 :(得分:5)

适合上述解决方案的模式类型为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 :(得分:5)

您可以为每个对象类型创建工厂,并且这些工厂可以具有将标准作为参数的函数,并且如果满足参数则返回IGruntWorker(否则返回null)。

然后你可以创建一个这些工厂的列表并循环遍历它们(对不起,我是一个c#人):

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

当需要新标准时,您只需将其添加到工厂列表中,无需修改循环。

可能有一些更美丽的方式

我的2美分

答案 3 :(得分:2)

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

这使您无需重新编译主应用程序,但是您必须在其他DLL中构建您的工作人员,然后有办法告诉您的工厂使用哪个。

这里有一些非常快速和脏的伪代码来解决这个问题:

假设您有一个名为Workers.DLL的DLL程序集

使用名为Name的字符串属性设置名为WorkerTypeAttribute的属性,以及能够设置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.
                }
            }
        }
    }

我确信还有其他一些如何做到这一点的例子,但是如果你需要更多指针,请告诉我。关键是你的所有工人都需要有一个共同的父或接口,以便你可以用同样的方式调用它们。 (即所有工作人员都需要一个通用的“执行”方法或可以从工厂调用的东西,或者在任何使用该对象的地方。

答案 4 :(得分:1)

如果你可以使用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;
}

您可以在单独的模块中重新编译表,而无需重新编译选择代码。事实上,如果你有野心,你甚至可以在运行时根据命令行参数或文件内容构建表...

答案 5 :(得分:1)

您可以使用访客模式的变体吗?称之为工厂访客(也许)

原谅伪代码,但我的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()

答案 6 :(得分:0)

我认为只要您最有可能的标准是先订购,以允许运行时跳过剩下的情况,这很好。

如果你的顾虑只是可读性,你可以使用三元运算符,或者如果标准评估只是==,你可以使用switch语句。

答案 7 :(得分:0)

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

   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
}

答案 8 :(得分:0)

我认为很多都取决于你的'条件'是多么可预测。你的'成长IF'本质上是一个工厂,也许重构它自己的方法或类会有所帮助,但它可能仍然是一个成长的IF。如果你的条件是你无法预测的事情,比如“if joe.is.on.fire”或“if x == 2”或“if!shuttle.is.launched”那么你就会被IF所困扰。

这些uber-IF的一个坏处是它们可能对您的应用程序产生的范围。也就是说,你需要打电话/触摸/检查以确定哪个'如果'应该是真的?你最终可能会有大量的全球性或大量参数传递到你的“工厂”。我之前做过的一件事就是实现一个包含boolean-delegates(Func)和类型数组的各种工厂。我会在初始化时注册布尔委托和类型,并在工厂中通过调用每个委托的列表进行迭代,直到我得到'true'然后实例化该类型。这对我来说效果很好,因为我能够在不编辑工厂的情况下“注册”新条件。

只是一个想法

答案 9 :(得分:0)

我知道你的.NET,但这就是我在Java web应用程序中做类似的事情,我的'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);
}

答案 10 :(得分:0)

您可以使用反射来查找给定类型的构造函数,并通过构造函数创建实例。原因,施工人员必须遵循某种模式。在上面的示例中,所有都是默认构造函数。