我正在尝试为我的公司创建一个基于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
注册自己?
答案 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)
那就是说,我可能只是使用多态(因此使类型系统适合我)。让每个报告实现一个FindDetails方法(我将它们从Report抽象类继承),因为无论如何你将以几种细节查找器结束。这也模拟了函数式语言的模式匹配和代数数据类型。