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

C#:抽象策略基类,用作策略对象的抽象工厂,c#,factory-pattern,strategy-pattern,C#,Factory Pattern,Strategy Pattern,我正在尝试为我的公司创建一个基于网络的工具,本质上,它使用地理输入生成表格结果。目前,三个不同的业务领域使用我的工具并收到三种不同的输出。幸运的是,所有输出都基于主表-子表的相同思想,它们甚至共享一个公共主表 不幸的是,在每种情况下,子表的相关行都包含截然不同的数据。因为这是唯一的争论点,所以我将FetchChildData方法提取到一个名为DetailFinder的单独类中。因此,我的代码如下所示: DetailFinder DetailHandler; if (ReportType == "

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

不幸的是,在每种情况下,子表的相关行都包含截然不同的数据。因为这是唯一的争论点,所以我将
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);
DetailFinder DetailHandler = DetailFinder.Parse(ReportType);
其中PlanningFinder、OperationsFinder和MaintenanceFinder都是DetailFinder的子类

我刚刚被要求为另一个业务领域添加支持,如果阻止趋势,我不想继续这种
if
block趋势。我更希望有一个如下所示的解析方法:

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);
DetailFinder DetailHandler = DetailFinder.Parse(ReportType);

但是,我不知道如何让
DetailFinder
知道哪些子类处理每个字符串,甚至知道存在哪些子类,而不只是将if块移动到
Parse
方法。子类是否有办法在抽象的
DetailFinder
中注册自己?

只要大的
语句
阻塞或
开关
语句或任何它只出现在一个地方的东西,这对可维护性并没有坏处,所以不必为此担心

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


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

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

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

    ...
我不确定这是否比您的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;
      }
}
公共类详细信息查找器
{
私有静态词典创建者;
静态DetailFinder()
{
创建者=新字典();
添加(“计划”,typeof(PlanningFinder));
...
}
公共静态DetailFinder创建(字符串类型)
{
类型t=创建者[类型];
返回Activator.CreateInstance(t)作为DetailFinder;
}
}

为了避免不断增长的if..else块,您可以将其切换,以便各个查找程序向factory类注册它们处理的类型

初始化时的工厂类需要发现所有可能的查找程序,并将它们存储在hashmap(字典)中。这可以通过反射和/或使用MarkSeemann建议的托管可扩展性框架来实现


但是,要小心,不要让这过于复杂。更愿意做最简单的事情,现在就可以工作,以便在需要时进行反射。如果您只需要再多一个finder类型,请不要构建复杂的自配置框架;)

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

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


我认为有关“插件”模式的信息在您的情况下会很有用:

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

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

IoC.Register<DetailHandler, PlanningFinder>("Planning");
IoC.Register<DetailHandler, OperationsFinder>("Operations");
...
国际奥委会注册(“规划”); 国际奥委会注册(“运营”); ...
然后:

DetailHandler handler = IoC.Resolve<DetailHandler>("Planning");
DetailHandler=IoC.Resolve(“计划”);
关于这个主题的一些变化

您可以查看以下IoC实现:


正如马克所说,一个大的if/开关块并不坏,因为它将全部放在一个地方(所有的计算机科学基本上都是关于在某种空间中获得相似性)


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

出于好奇,如果创建者受到保护,DetailFinder的子类是否可以在自己的静态构造函数中将自己添加到创建者中?在这种情况下,我可能会提供一个注册方法,而不是让每个类访问映射本身。使用单个注册方法使线程安全会容易得多,这是有意义的。但是,有没有保证在调用基类的Parse方法之前,所有的子类都会执行它们的静态构造函数?没有,我认为你不能保证。唯一的保证是静态构造函数在第一次使用该类之前就存在。