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

3
我正在尝试为我的公司创建一个基于Web的工具,它使用地理输入来生成表格结果。目前,三个不同的业务领域使用我的工具,并接收三种不同类型的输出。幸运的是,所有的输出都基于相同的Master Table-Child Table的思想,甚至共享一个常见的Master Table。
不幸的是,在每种情况下,Child Table的相关行包含大量不同的数据。因为这是唯一的争议点,我将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个回答

3

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

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

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

接着:

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

以下是一些关于IoC的变体。

您可以查看以下IoC实现:


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

出于纯好奇,如果将Creators设为受保护的,那么DetailFinder的子类在它们自己的静态构造函数期间能否将自己添加到Creators中? - DonaldRay
在这种情况下,我可能会提供一个注册方法,而不是让每个类都访问地图本身。使用单个注册方法可以更轻松地实现线程安全。 - tvanfosson
这很有道理。但是,所有的子类在调用基类的Parse方法之前执行它们的静态构造函数有保证吗? - DonaldRay
不,我认为你不能保证这一点。唯一的保证是静态构造函数在类第一次使用之前被调用。 - tvanfosson

0

您可以使用反射机制。以下是 DetailFinder 的 Parse 方法的示例代码(请记得在代码中添加错误检查):

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

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

我认为关于“插件”模式的信息对你很有用:EAA设计模式之插件


0
只要大的if块或switch语句或其他出现在一个地方,对于可维护性来说并不会很糟糕,因此不用为此担心。
然而,当涉及到可扩展性时情况就不同了。如果您真正希望新的DetailFinders能够自行注册,您可能需要查看Managed Extensibility Framework,它基本上允许您在“插件”文件夹或类似位置中放置新的程序集,核心应用程序将自动获取新的DetailFinders。
但是,我不确定这是否是您真正需要的可扩展性程度。

0
为了避免一个不断增长的if..else块,您可以将其反转,使各个查找器向工厂类注册它们处理的类型。
在初始化时,工厂类需要发现所有可能的查找器并将它们存储在哈希映射表(字典)中。这可以通过反射和/或使用托管可扩展性框架来完成,正如Mark Seemann所建议的那样。
然而,请注意不要使此过程过于复杂。最好采用可能的最简单方法来解决问题,并考虑到以后需要重构。如果您只需要一个更多的查找器类型,请不要构建一个复杂的自配置框架;)

0

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

话虽如此,我可能会选择使用多态性(从而使类型系统为我工作)。让每个报告实现一个FindDetails方法(我会让它们继承自一个Report抽象类),因为你最终会得到几种不同的详细信息查找器。这也模拟了函数式语言中的模式匹配和代数数据类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接