3

我正在尝试为我的公司创建一个基于 Web 的工具,该工具本质上是使用地理输入来生成表格结果。目前,三个不同的业务领域使用我的工具并获得三种不同的输出。幸运的是,所有的输出都基于相同的主表 - 子表的想法,它们甚至共享一个共同的主表。

不幸的是,在每种情况下,子表的相关行都包含截然不同的数据。因为这是唯一的争论点,所以我将一个FetchChildData方法提取到一个名为DetailFinder. 结果,我的代码如下所示:

DetailFinder DetailHandler;
if (ReportType == "Planning")
  DetailHandler = new PlanningFinder();
else if (ReportType == "Operations")
  DetailHandler = new OperationsFinder();
else if (ReportType == "Maintenance")
  DetailHandler = new MaintenanceFinder();
DataTable ChildTable = DetailHandler.FetchChildData(Master);

其中 PlanningFinder、OperationsFinder 和 MaintenanceFinder 都是 DetailFinder 的子类。

我刚刚被要求添加对另一个业务领域的支持,并且不愿意继续这种if块趋势。我更希望有一个如下所示的解析方法:

DetailFinder DetailHandler = DetailFinder.Parse(ReportType);

但是,我不知道如何DetailFinder知道哪些子类处理每个字符串,甚至不知道存在哪些子类,而不仅仅是将 if 块转移到Parse方法中。有没有办法让子类在 abstract 中注册自己DetailFinder

4

6 回答 6

3

您可以使用 IoC 容器,其中许多允许您使用不同的名称或策略注册多个服务。

例如,使用假设的 IoC 容器,您可以这样做:

IoC.Register<DetailHandler, PlanningFinder>("Planning");
IoC.Register<DetailHandler, OperationsFinder>("Operations");
...

进而:

DetailHandler handler = IoC.Resolve<DetailHandler>("Planning");

这个主题的一些变化。

您可以查看以下 IoC 实现:

于 2010-01-04T23:17:18.387 回答
1

您可能希望使用类型映射到创建方法:

public class  DetailFinder
{
    private static Dictionary<string,Func<DetailFinder>> Creators;

    static DetailFinder()
    {
         Creators = new Dictionary<string,Func<DetailFinder>>();
         Creators.Add( "Planning", CreatePlanningFinder );
         Creators.Add( "Operations", CreateOperationsFinder );
         ...
    }

    public static DetailFinder Create( string type )
    {
         return Creators[type].Invoke();
    }

    private static DetailFinder CreatePlanningFinder()
    {
        return new PlanningFinder();
    }

    private static DetailFinder CreateOperationsFinder()
    {
        return new OperationsFinder();
    }

    ...

}

用作:

DetailFinder detailHandler = DetailFinder.Create( ReportType );

我不确定这是否比您的 if 语句好得多,但它确实使它易于阅读和扩展。Creators只需在地图中添加一个创建方法和一个条目。

另一种选择是存储报告类型和查找器类型的映射,然后在类型上使用 Activator.CreateInstance 如果您总是简单地调用构造函数。如果对象的创建更复杂,上面的工厂方法细节可能更合适。

public class DetailFinder
{
      private static Dictionary<string,Type> Creators;

      static DetailFinder()
      {
           Creators = new Dictionary<string,Type>();
           Creators.Add( "Planning", typeof(PlanningFinder) );
           ...
      }

      public static DetailFinder Create( string type )
      {
           Type t = Creators[type];
           return Activator.CreateInstance(t) as DetailFinder;
      }
}
于 2010-01-04T22:38:41.383 回答
0

只要if大块或switch语句或任何它只出现在一个地方,它对可维护性来说还不错,所以不要为此担心。

但是,当涉及到可扩展性时,情况就不同了。如果您真的希望新的 DetailFinder 能够自行注册,您可能需要查看托管可扩展性框架,它本质上允许您将新程序集放入“加载项”文件夹或类似文件夹中,然后核心应用程序将自动拾取新的 DetailFinder。

但是,我不确定这是否是您真正需要的可扩展性。

于 2010-01-04T22:33:44.330 回答
0

为了避免不断增长的 if..else 块,您可以将其切换,以便各个查找器使用工厂类注册他们处理的类型。

初始化的工厂类将需要发现所有可能的查找器并将它们存储在哈希图(字典)中。这可以通过反射和/或使用 Mark Seemann 建议的托管可扩展性框架来完成。

但是 - 小心不要让这个过于复杂。宁愿做现在可能工作的最简单的事情,以便在需要时进行反思。如果您只需要一种查找器类型,就不要去构建一个复杂的自配置框架;)

于 2010-01-04T22:40:02.447 回答
0

您可以使用反射。DetailFinder 的 Parse 方法有一个示例代码(记得在该代码中添加错误检查):

public DetailFinder Parse(ReportType reportType)
{
    string detailFinderClassName = GetDetailFinderClassNameByReportType(reportType);
    return Activator.CreateInstance(Type.GetType(detailFinderClassName)) as DetailFinder;
}

方法GetDetailFinderClassNameByReportType可以从数据库、配置文件等中获取类名。

我认为有关“插件”模式的信息对您的情况很有用:EAA 的 P:插件

于 2010-01-04T22:44:47.697 回答
0

就像 Mark 说的那样,一个大的 if/switch 块还不错,因为它都在一个地方(所有的计算机科学基本上都是关于在某种空间中获得相似性)。

也就是说,我可能只使用多态性(从而使类型系统为我工作)。让每个报告都实现一个 FindDetails 方法(我会让它们从 Report 抽象类继承),因为无论如何你都会以几种细节查找器结束。这也模拟了来自函数式语言的模式匹配和代数数据类型。

于 2010-01-05T01:19:56.383 回答