【问题标题】:C#: Abstract Strategy base class serving as Abstract Factory for Strategy objectsC#:抽象策略基类作为策略对象的抽象工厂
【发布时间】:2010-01-04 22:25:48
【问题描述】:

我正在尝试为我的公司创建一个基于 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 注册自己?

【问题讨论】:

    标签: c# factory-pattern strategy-pattern


    【解决方案1】:

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

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

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

    然后:

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

    这个主题的一些变化。

    您可以查看以下 IoC 实现:

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 纯粹出于好奇,如果 Creators 受到保护,DetailFinder 的子类是否可以在其自己的静态构造函数中将自己添加到 Creators 中?
      • 在这种情况下,我可能会提供一个注册方法,而不是让每个类访问地图本身。使用单个注册方法使线程安全会容易得多。
      • 这是有道理的。但是,是否保证所有子类都会在调用基类的 Parse 方法之前执行其静态构造函数?
      • 不,我认为你不能保证。唯一的保证是静态构造函数在第一次使用类之前。
      【解决方案3】:

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

      但是,在可扩展性方面,情况就不同了。如果您真的希望新的 DetailFinder 能够自行注册,您可能需要查看Managed Extensibility Framework,它实质上允许您将新程序集放入“加载项”文件夹或类似文件夹中,然后核心应用程序将自动拾取新的 DetailFinder。

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

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          【解决方案5】:

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

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

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

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

          【讨论】:

            【解决方案6】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-12-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多