我们的C#应用程序生成各种类型的报告。每种类型的报告都有一个xml架构,用于定义可用的选项和功能,因此用户可以创建一个描述所需报告的xml文档。有多个报告类型共有的元素,并且某些元素之间存在相互依赖关系。
我们目前有一个完整的静态方法来处理xml的解析。它将采用模式验证的文档,并返回表示报告类型及其配置选项的对象。该课程看起来像这样:
public class ReportFactory
{
//let's say 4 types of reports
public static ReportType1 CreateReportType1(XDocument document)
{
//logic to create this type of report, calling private methods in this class
}
....
public static ReportTypeN CreateReportTypeN(XDocument document)
{
//logic to create this type of report
}
//several dozen private methods, which get called from the public methods or from each other
private static Feature1 CreateFeature1(XElement element)
{
//create some feature of a report
}
private static FeatureN CreateFeatureN(XElement element, FeatureM m)
{
//create some feature which relies on another previously built feature
}
private static FeatureX CreateFeatureX(ReportType2 report, XElement elementForX, XElement elementRelatedToX)
{
//create some feature, which relies on
//more than one element and/or a partially built report of a given type
}
private static void UpdateFeatureNWithStuffFromFeatureM(FeatureN n, FeatureM m)
{
//modify parts of the built report, based on some other parts of the report
}
...
}
我认为其目的是封装xml结构的细节,我认为这样做。它也不会受到重复代码的影响。但它非常庞大且难以阅读,并且随着我们添加更多功能而变得更糟。它也很难测试,因为它在很大程度上依赖于按正确顺序完成的事情。
我想重构它,但到目前为止,我唯一能想到的就是把它分成多个类;比方说,每个报告类型的一个类和一个常见的东西的额外帮助类。但它仍然会很混乱,甚至可能更难阅读。是否有一个好方法来组织这样的事情?任何可能有帮助的模式?我看了一堆创作模式,并没有真正找到任何合适的东西。
更新:抱歉,我没有时间或预算来实际完成这一重构,但感谢您的建议。我想的越多,我就越喜欢(就像)责任链的想法。起点(公共函数)将创建返回对象并填充一些基本内容,然后将对象和xml移交给下一个部分。每件作品都会知道对象的哪些部分和它需要的xml,每个部分都可以通过查看对象的变化来独立测试。
答案 0 :(得分:0)
我猜你会需要几种模式和原则来增强你的课程。您已经在使用工厂模式。您似乎要描述的问题是处理特定的活动顺序并将您的类分解为更小的块。
为了处理活动的顺序,看起来你可以use a chain of responsibility pattern.。
关于解决方案的“部分”由于依赖性而可测试性的注意事项,其中一部分是测试设置(您需要更好的模拟或存根来支持方法的活动)以及可能更好的设计。你努力测试应用程序。
答案 1 :(得分:0)
我希望有一些方法可以利用继承并使用适当的构造函数而不是工厂(我不认为需要工厂)。此外,将报表特定功能仅应用于它们应用的报表,以及仅在其应用的功能中使用功能特定功能,这将是一件好事。像这样:
public class Report
{
protected Report(XDocument document)
{
// Any code common to creating the majority of reports
}
protected class Feature
{
private XElement element;
public Feature(XElement element)
{
// Any code common to creating the majority of features
}
public Feature(Feature feature)
{
element = feature.element;
// Any code common to creating features from other features
}
}
public class Feature1 : Feature
{
public Feature1(XElement element)
: base(element)
{
// Any code specific to creating Feature1
}
}
public class FeatureN : Feature
{
public FeatureN(FeatureM feature) : base(feature)
{
// Any code specific to creating a featureN from a featureM
}
public void UpdateFromFeatureM(FeatureM feature)
{
//modify parts of the built report, based on some other parts of the report
}
}
public class FeatureM : Feature
{
}
}
public class ReportType1 : Report
{
public ReportType1(XDocument document)
: base(document)
{
// Code specific to creating ReportType1
}
}
public class ReportType2 : Report
{
public ReportType2(XDocument document)
: base(document)
{
// Code specific to creating ReportType2
}
public class FeatureX
{
public FeatureX(ReportType2 report, XElement element, XElement relatedElement)
{
//create some feature, which relies on
//more than one element and/or a partially built report of a given type
}
}
}
答案 2 :(得分:0)
一种相当常见的方法是使用策略模式。你可以有一个返回IReport的IReportBuilder接口。然后,每个具体的报表生成器将封装构建报表的细节(可能具有公共代码的基类)。同样,“特征”可以遵循这种模式,具体的特征将具体类注入具体的报告中。
我不确定客户如何选择报告;但是工厂可以用来实例化一个正确的具体报告构建器,基于一个密钥(这可能更好用一个例子)。
答案 3 :(得分:0)
我会尝试以下
ReportType
抽象和各自的具体类。 CreateReportTypeX()
的返回类型将是抽象类型。报告枚举可以帮助将CreateReportX()
统一到一个CreateReport()
Feature
抽象和具体要素类。CreateFeatureX()
的参数对象和要素枚举。ReportFactory
应仅处理CreateReport()
。对于FeatureFactory
,我们可能需要CreateFeature()
。