C#:抽象策略基类,用作Strategy对象的抽象工厂

时间:2010-01-04 22:25:49

标签: c# factory-pattern strategy-pattern

我正在尝试为我的公司创建一个基于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方法。有没有办法让子类用抽象DetailFinder注册自己?

6 个答案:

答案 0 :(得分:3)

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

例如,使用假设的IoC容器,您可以执行此操作:

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

然后:

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

这个主题的一些变化。

您可以查看以下IoC实现:

答案 1 :(得分: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;
      }
}

答案 2 :(得分:0)

只要大if块或switch语句或其他任何内容只出现在一个地方,它对可维护性来说并不坏,所以不要因为这个原因而担心。

但是,在可扩展性方面,情况有所不同。如果您真的希望新的DetailFinder能够自己注册,您可能需要查看Managed Extensibility Framework,它基本上允许您将新程序集放入“加载项”文件夹或类似文件夹,以及核心应用程序然后会自动选择新的DetailFinder。

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

答案 3 :(得分:0)

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

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

然而 - 要小心使这个过于复杂。喜欢做最简单的事情,现在可能会工作,以便在需要时进行重新设计。如果您只需要一个查找程序类型,请不要构建复杂的自我配置框架;)

答案 4 :(得分:0)

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

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

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

我认为有关“插件”模式的信息对您的案例非常有用:P of EAA: Plugin

答案 5 :(得分:0)

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

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