如何解決“成長如果聲明”的問題?

[英]How to solve the “Growing If Statement” problem?


I've been doing some reading about design patterns and wanted some perspective. Consider the following:

我一直在閱讀有關設計模式的一些內容,並想要一些觀點。考慮以下:

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()

The above code grows each time a new Criteria arises. I've seen code like this all over the place and in ignorance wrote some of it myself. How should this be solved? Does this kind of anti-pattern have a more "formal" name? Thanks for your help!

每次出現新標准時,上述代碼都會增長。我已經看到了這樣的代碼,並且無知中自己寫了一些代碼。該如何解決?這種反模式是否具有更“正式”的名稱?謝謝你的幫助!

Edit: Another consideration is I want to avoid having to recompile the existing implementations of IGruntWorker simply to add a new implementation.

編輯:另一個考慮因素是我想避免重新編譯IGruntWorker的現有實現只是為了添加一個新的實現。

11 个解决方案

#1


2  

If you are using .NET you could build it with reflection instead. For example, if you were creating a plugin system then you would have a folder to drop plugin DLLs into. Then your factory would look at the available DLLs, examine each one for the appropriate reflection attributes, then match those attributes against whatever string was passed in to decide which object to select and invoke.

如果您使用的是.NET,則可以使用反射來構建它。例如,如果您正在創建一個插件系統,那么您將有一個文件夾來刪除插件DLL。然后你的工廠會查看可用的DLL,檢查每個DLL的相應反射屬性,然后將這些屬性與傳入的任何字符串相匹配,以決定選擇和調用哪個對象。

This keeps you from having to recompile your main app, though you'll have to build your workers in other DLLs and then have a way to tell your factory which one to use.

這使您無需重新編譯主應用程序,但您必須在其他DLL中構建您的工作程序,然后有辦法告訴您的工廠使用哪個。

Here's some really fast and dirty pseudo code to get the point across:

這里有一些非常快速和臟的偽代碼來說明問題:

Assuming you have a DLL assembly called Workers.DLL

假設您有一個名為Workers.DLL的DLL程序集

Set up an attribute called WorkerTypeAttribute with a string property called Name, and the constructor to be able to set that Name property.

使用名為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;
    }
}

You'd then apply this attribute to any worker class that you've defined like:

然后,您可以將此屬性應用於您定義的任何工作類:

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

Then in your app's worker factory you'd write code like:

然后在你的app工作者工廠中,你會編寫如下代碼:

 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'm sure there are other examples of how to do this out there, but if you need some more pointers, let me know. The key is that all of your workers need to have a common parent or interface so that you can invoke them the same way. (I.e. all of your workers need a common "Execute" method or something that can be called from the factory, or wherever you use the object.

我確定還有其他一些如何做到這一點的例子,但如果你需要更多指針,請告訴我。關鍵是你的所有工人都需要有一個共同的父或接口,以便你可以用同樣的方式調用它們。 (即,您的所有工作人員都需要一個通用的“執行”方法或可以從工廠調用的方法,或者您使用該對象的任何地方。

#2


7  

That sort of logic is often encapsulated using the Factory method pattern. (See the ImageReaderFactory example under Encapsulation.)

這種邏輯通常使用Factory方法模式封裝。 (請參閱Encapsulation下的ImageReaderFactory示例。)

#3


5  

The type of pattern that would suit the above solution would be the Factory Pattern. You have a situation where you don't need to know the concrete type of object you require, it just has to implement IGruntWorker. So you create a factory which takes in a criteria and based on that criteria you would return the specific IGruntWorker object. It is usually a good idea to map the criteria to some identifier i.e. an enumeration or constant for readability e.g.

適合上述解決方案的模式類型是工廠模式。在某種情況下,您不需要知道所需的具體對象類型,只需實現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();
        }
    }
}

So in your case you could have a small helper method that works out the correct type of Worker required based on the criteria. This could even be wrapped up in a read-only property which you just pass into the factory.

因此,在您的情況下,您可以使用一個小幫助方法,根據條件確定正確的工作類型。這甚至可以包含在您只是傳遞到工廠的只讀屬性中。

#4


5  

You could create Factories for each object type, and those factories could have a function that takes criterias as parameter and returns a IGruntWorker if the parameters are satisfied (or null otherwise).

您可以為每個對象類型創建工廠,並且這些工廠可以具有將標准作為參數的函數,並且如果滿足參數則返回IGruntWorker(否則返回null)。

You could then create a list of those factories and loop through them like (sorry I'm a c# guy):

然后你可以創建一個這些工廠的列表並循環遍歷它們(對不起,我是一個c#人):

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

When a new criteria is needed, you only add it to the list of factories, no need to modify the loop.

當需要新標准時,您只需將其添加到工廠列表中,無需修改循環。

There are probably some more beautiful ways

可能有一些更美麗的方式

My 2 cents

我的2美分

#5


1  

If you can define an object with a checkCriteria method, then you can make this code table-driven. I don't know C#, so bear with me on the syntax:

如果可以使用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 */
}

Then some of the objects in the table look like this

然后表中的一些對象看起來像這樣

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

You can recompile the table in a separate module without having to recompile the selection code. In fact, if you get ambitious, you could even build the table at run time based on command-line arguments or the contents of a file...

您可以在單獨的模塊中重新編譯表,而無需重新編譯選擇代碼。實際上,如果你有野心,你甚至可以在運行時根據命令行參數或文件內容構建表...

#6


1  

Could you use a variant of the visitor pattern instead? Call it the factory visitor (perhaps)

您可以使用訪客模式的變體嗎?稱之為工廠訪客(也許)

excuse the pseudo-code, but my VB is rusty

原諒偽代碼,但我的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  

I think as long as your most likely criteria are ordered first to allow the runtime to jump the rest of the cases, this is fine.

我認為只要你最有可能的標准是先命令允許運行時跳過其余的情況,這很好。

If your concern is just readability you could use the ternary operator or if the criteria evaluations are just ==, you could use a switch statement.

如果你關注的只是可讀性,你可以使用三元運算符,或者如果標准評估只是==,你可以使用switch語句。

#8


0  

I think this pattern is fine, so long as your criteria and operations are single lines/method calls. This is easy to read and accurately reflects your logic:

我認為這種模式很好,只要你的標准和操作是單行/方法調用。這很容易閱讀並准確反映您的邏輯:

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

Even if there are 20 different conditions, it probably is an accurate reflection of some complex business logic of your application.

即使有20種不同的條件,它也可能准確地反映了應用程序的一些復雜業務邏輯。

On the other hand, this is a readability disaster

另一方面,這是一種可讀性災難

if (  ((A && B) || C &&
      (D == F) || (F == A)))
{
   AA;
   BB;
   //200 lines of code
}
else if ( (A || D) && B)
{
  // 200 more lines
}

#9


0  

I think a lot of it depends on how predictable your 'conditions' are. Your 'growing IF' IS essentially a factory, and perhaps refactoring it out to its own method or class would help, but it may STILL be a growing-IF. If your conditions are things you cannot predict, like "if joe.is.on.fire" or "if x==2" or "if !shuttle.is.launched" then you're stuck with IF's.

我認為很大程度上取決於你的“條件”是多么可預測。你的'成長IF'本質上是一個工廠,也許重構它自己的方法或類會有所幫助,但它可能仍然是一個不斷增長的IF。如果您的條件是您無法預測的事情,例如“if joe.is.on.fire”或“if x == 2”或“if!shuttle.is.launched”那么您就會陷入IF的困境。

One bad thing about these uber-IF's is the scope they may have over your application. That is, what all do you need to call/touch/check to determine which 'if' should be true? You might end up having tons of global cruft or lots of parameters to pass to your 'factory'. One thing I did a little while ago to help with this was implement a factory of sorts that contained an array of boolean-delegates (Func) and types. I would register boolean delegates and types at initialization-time, and iterate thru the list in the factory calling each delegate until I got a 'true' and then instantiated that type. That worked well for me becuase I was able to 'register' new conditions without editing the factory.

關於這些超級IF的一個壞處是它們可能對您的應用程序的范圍。也就是說,您需要調用/觸摸/檢查以確定哪個'if'應該是真的?你最終可能會有大量的全球性或大量參數傳遞到你的“工廠”。我前一段時間做過的一件事就是實現一個包含boolean-delegates(Func)和類型數組的各種工廠。我會在初始化時注冊布爾委托和類型,並在工廠中通過調用每個委托的列表進行迭代,直到我得到'true'然后實例化該類型。這對我來說很有效,因為我能夠在沒有編輯工廠的情況下“注冊”新的條件。

Just an idea

只是一個想法

#10


0  

I know your .NET but this is how I do something similar in a Java web application, where my 'if-thens' were growing....still requires a recompile but easy to add other actions or in your case grunt workers.

我知道你的.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);
}

#11


0  

You can use reflection to find a constructor of a given type and create the instance by the constructor. Of cause, constructors must follow certain pattern. In your example above, all are default constructors.

您可以使用反射來查找給定類型的構造函數,並通過構造函數創建實例。原因是,構造者必須遵循某種模式。在上面的示例中,所有都是默認構造函數。


注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2010/06/08/78db16493d7f4bbb60785e28023c6e4f.html



 
  © 2014-2022 ITdaan.com 联系我们: