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

Ach*_*les 19 .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()
Run Code Online (Sandbox Code Playgroud)

每次出现新标准时,上述代码都会增长.我已经看到了这样的代码,并且无知中自己写了一些代码.该如何解决?这种反模式是否具有更"正式"的名称?谢谢你的帮助!

编辑:另一个考虑因素是我想避免重新编译现有的IGruntWorker简单添加新实现的实现.

Bil*_*ard 7

这种逻辑通常使用Factory方法模式封装.(请参阅Encapsulation下的ImageReaderFactory示例.)

  • 据我所知,"工厂方法"是他现在已有的.我不确定,除了F#之外,任何.net语言的现有代码都可以更简洁.模式匹配可能会有很大帮助. (3认同)

Jam*_*mes 5

适合上述解决方案的模式类型是工厂模式.你有一种情况,你不需要知道你需要的具体类型的对象,它只需要实现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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,在您的情况下,您可以使用一个小帮助方法,根据条件计算出所需的正确类型的Worker.这甚至可以包含在您只是传递到工厂的只读属性中.

  • 是的,但它的目的是明确和孤立的 - 大多数人会认识到它的工厂类,它的构造变化了工人类型并简化了应用程序代码的使用. (4认同)
  • 我唯一不喜欢这个解决方案的是它似乎将问题重构为另一个类.我仍然有一个不断增长的转换声明. (3认同)
  • 看看我是如何使用IOC容器实现工厂的(接受的答案).这将删除工厂的If语句.您将所有组件布线逻辑保留在IoC引导程序中.您甚至可能在xml文件中具有此逻辑... http://stackoverflow.com/questions/2965824/inject-different-repository-depending-on-a-querystring-derive-controller-and-in (3认同)

Mik*_*ier 5

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

然后你可以创建一个这些工厂的列表并循环遍历它们(抱歉,我是ac#guy):

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

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

可能有一些更美丽的方式

我的2美分

  • 怎么样:`o = factories.Select(f => f.Create(criteria)).FirstOrDefault();` (3认同)

Dav*_*ers 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以将此属性应用于您定义的任何工作类,如下所示:

[WorkerType("CogWorker")]
public class CogWorker : WorkerBase {}
Run Code Online (Sandbox Code Playgroud)

然后在应用程序的工作工厂中,您将编写如下代码:

 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.
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

我确信还有其他示例可以说明如何执行此操作,但如果您需要更多指示,请告诉我。关键是您的所有工作人员都需要有一个共同的父级或接口,以便您可以以相同的方式调用它们。(即,您的所有工作人员都需要一个通用的“执行”方法或可以从工厂或您使用该对象的任何地方调用的方法。