简体   繁体   English

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

[英]C#: Abstract Strategy base class serving as Abstract Factory for Strategy objects

I am trying to create a web-based tool for my company that, in essence, uses geographic input to produce tabular results. 我正在尝试为我的公司创建一个基于Web的工具,实质上,它使用地理输入来生成表格结果。 Currently, three different business areas use my tool and receive three different kinds of output. 目前,三个不同的业务领域使用我的工具并接收三种不同的输出。 Luckily, all of the outputs are based on the same idea of Master Table - Child Table, and they even share a common Master Table. 幸运的是,所有输出都基于Master Table - Child Table的相同概念,它们甚至共享一个共同的Master Table。

Unfortunately, in each case the related rows of the Child Table contain vastly different data. 不幸的是,在每种情况下,子表的相关行包含非常不同的数据。 Because this is the only point of contention I extracted a FetchChildData method into a separate class called DetailFinder . 因为这是唯一的争用点,我将FetchChildData方法提取到一个名为DetailFinder的单独类中。 As a result, my code looks like this: 结果,我的代码如下所示:

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

Where PlanningFinder, OperationsFinder, and MaintenanceFinder are all subclasses of DetailFinder. PlanningFinder,OperationsFinder和MaintenanceFinder都是DetailFinder的子类。

I have just been asked to add support for another business area and would hate to continue this if block trend. 我刚被要求增加对另一个业务领域的支持,并且if阻止趋势, if不愿继续这样做。 What I would prefer is to have a parse method that would look like this: 我更喜欢的是有一个看起来像这样的解析方法:

DetailFinder DetailHandler = DetailFinder.Parse(ReportType);

However, I am at a loss as to how to have DetailFinder know what subclass handles each string, or even what subclasses exist without just shifting the if block to the Parse method. 但是,我DetailFinder知道如何让DetailFinder知道哪个子类处理每个字符串,甚至是什么子类存在而不只是将if块移动到Parse方法。 Is there a way for subclasses to register themselves with the abstract DetailFinder ? 有没有办法让子类用抽象的DetailFinder注册自己?

You could use an IoC container, many of them allows you to register multiple services with different names or policies. 您可以使用IoC容器,其中许多容器允许您使用不同的名称或策略注册多个服务。

For instance, with a hypothetical IoC container you could do this: 例如,使用假设的IoC容器,您可以这样做:

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

and then: 然后:

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

some variations on this theme. 这个主题的一些变化。

You can look at the following IoC implementations: 您可以查看以下IoC实现:

You might want to use a map of types to creational methods: 您可能希望使用类型映射来创建方法:

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

    ...

} }

Used as: 用作:

DetailFinder detailHandler = DetailFinder.Create( ReportType );

I'm not sure this is much better than your if statement, but it does make it trivially easy to both read and extend. 我不确定这比if语句好多了,但它确实使得阅读和扩展都很容易。 Simply add a creational method and an entry in the Creators map. 只需在Creators地图中添加创建方法和条目即可。

Another alternative would be to store a map of report types and finder types, then use Activator.CreateInstance on the type if you are always simply going to invoke the constructor. 另一种方法是存储报告类型和查找程序类型的映射,然后在类型上使用Activator.CreateInstance,如果您总是只是调用构造函数。 The factory method detail above would probably be more appropriate if there were more complexity in the creation of the object. 如果对象的创建更复杂,上面的工厂方法细节可能更合适。

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

As long as the big if block or switch statement or whatever it is appears in only one place, it isn't bad for maintainability, so don't worry about it for that reason. 只要大块if块或switch语句或它只出现在一个地方,它对可维护性来说并不坏,所以不要因为这个原因而担心它。

However, when it comes to extensibility, things are different. 但是,在可扩展性方面,情况有所不同。 If you truly want new DetailFinders to be able to register themselves, you may want to take a look at the Managed Extensibility Framework which essentially allows you to drop new assemblies into an 'add-ins' folder or similar, and the core application will then automatically pick up the new DetailFinders. 如果您真的希望新的DetailFinder能够自己注册,您可能需要查看Managed Extensibility Framework ,它实际上允许您将新程序集拖放到“加载项”文件夹或类似文件夹中,然后核心应用程序将会自动获取新的DetailFinders。

However, I'm not sure that this is the amount of extensibility you really need. 但是,我不确定这是否是您真正需要的可扩展性。

To avoid an ever growing if..else block you could switch it round so the individal finders register which type they handle with the factory class. 为了避免不断增长的if..else块,您可以将其切换为圆形,以便各个查找器注册它们与工厂类处理的类型。

The factory class on initialisation will need to discover all the possible finders and store them in a hashmap (dictionary). 初始化的工厂类需要发现所有可能的查找器并将它们存储在hashmap(字典)中。 This could be done by reflection and/or using the managed extensibility framework as Mark Seemann suggests. 这可以通过反思和/或使用托管可扩展性框架来完成,如Mark Seemann所建议的那样。

However - be wary of making this overly complex. 但是 - 要小心使这个过于复杂。 Prefer to do the simplest thing that could possibly work now with a view to refectoring when you need it. 喜欢做最简单的事情,现在可能会工作,以便在需要时进行重新设计。 Don't go and build a complex self-configuring framework if you'll only ever need one more finder type ;) 如果您只需要一个查找器类型,请不要构建复杂的自配置框架;)

You can use the reflection. 你可以使用反射。 There is a sample code for Parse method of DetailFinder (remember to add error checking to that code): 有一个DetailFinder的Parse方法的示例代码(记得在该代码中添加错误检查):

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

Method GetDetailFinderClassNameByReportType can get a class name from a database, from a configuration file etc. 方法GetDetailFinderClassNameByReportType可以从数据库,配置文件等获取类名。

I think information about "Plugin" pattern will be useful in your case: P of EAA: Plugin 我认为有关“插件”模式的信息对您的情况很有用: EA的EA:插件

Like Mark said, a big if/switch block isn't bad since it will all be in one place (all of computer science is basically about getting similarity in some kind of space). 就像马克所说的那样,一个很大的if / switch块并不坏,因为它都在一个地方(所有的计算机科学基本上都是关于在某种空间中获得相似性)。

That said, I would probably just use polymorphism (thus making the type system work for me). 也就是说,我可能只是使用多态(因此使类型系统适合我)。 Have each report implement a FindDetails method (I'd have them inherit from a Report abstract class) since you're going to end with several kinds of detail finders anyway. 让每个报告实现一个FindDetails方法(我将它们从Report抽象类继承),因为无论如何你将以几种细节查找器结束。 This also simulates pattern matching and algebraic datatypes from functional languages. 这也模拟了函数式语言的模式匹配和代数数据类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM