The*_*ONE 5 c# oop design-patterns
我会尝试尽可能明确,以防有更好的解决方案来解决我的问题而不是回答我的问题.
我在C#工作.
我有一个报告模板,可以包含任意数量的"功能".功能可能是信息表,饼图/条形图,列表等.我将报告生成为文本文件或PDF(将来可能还有其他选项).
到目前为止,我有一个IFeature接口,以及一些实现它的功能类型:ChartFeature,ListFeature等等.我读取了从数据库启用的功能列表,并将每个功能一起传递给方法以及数据ID,并且该方法返回一个填充IFeature的正确类型.
我还有一个IReportWriter接口TextReportWriter和PdfReportWriter实现.该接口有一个方法:AddFeature(IFeature).
问题是AddFeature每个作家最终看起来像:
public void AddFeature(IFeature)
{
InsertSectionBreakIfNeeded();
if(IFeature is TableFeature)
{
TableFeature tf = (TableFeature)feature;
streamWriter.WriteLine(tf.Title);
for(int row=0; row < tf.Data.First.Length; row++)
{
for(int column=0; i < tf.Data.Length; i++)
{
if(i != 0)
{
streamWriter.Write("|");
}
streamWriter.Write(feature.Data[column][row]);
}
}
}
else if(IFeature is ListFeature)
{
ListFeature lf = (ListFeature)feature;
streamWriter.Write(lf.Title + ": ");
bool first = true;
foreach(var v in lf.Data)
{
if(!first)
{
streamWriter.Write(", ");
}
else
{
first = false;
}
streamWriter.Write(v);
}
}
...
else
{
throw new NotImplementedException();
}
sectionBreakNeeded = true;
}
Run Code Online (Sandbox Code Playgroud)
在PDF编写器中,将修改上述内容以生成PDF表格单元格,文本框等.
这感觉很难看.我喜欢它好一点AddFeature(ListFeature){...},AddFeature(ChartFeature)因为至少它的编译时间已经检查过了,但实际上它只是将问题转移到外面,如果我正在调用IReportWriter if(feature is ...).
将显示代码移动到功能中只是为了解决问题,因为它需要知道它应该是写纯文本还是PDF.
任何建议,或者我最好只使用我拥有的东西而忽视我的感受?
编辑:填写一些条件,让人们更好地了解正在发生的事情.不要太担心这些例子中的确切代码,我只是把它写在了我的头顶.
您的问题的一般情况称为双调度 - 您需要根据两个参数的运行时类型调度方法,而不仅仅是一个("this"指针).
处理此问题的一种标准模式称为访客模式.它的描述追溯到最初的设计模式书,因此有很多例子和分析.
基本的想法是你有两个一般的东西 - 你有元素(你正在处理的东西)和访问元素的访问者.您需要对它们进行动态调度 - 因此调用的实际方法取决于元素和访问者的具体类型.
在C#中,有点按照你的例子,你可以像这样定义一个IFeatureVisitor接口:
public interface IFeatureVisitor {
void Visit(ChartFeature feature);
void Visit(ListFeature feature);
// ... etc one per type of feature
}
Run Code Online (Sandbox Code Playgroud)
然后,在您的IFeature界面中,添加"接受"方法.
public interface IFeature {
public void Accept(IFeatureVisitor visitor);
}
Run Code Online (Sandbox Code Playgroud)
您的功能实现将实现Accept方法,如下所示:
public class ChartFeature : IFeature {
public void Accept(IFeatureVisitor visitor) {
visitor.Visit(this);
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您的报表编写者将实现IVisitor接口,并在每种类型中执行它应该执行的任何操作.
要使用它,它看起来像这样:
var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
feature.Accept(writer);
}
writer.FinishUp();
Run Code Online (Sandbox Code Playgroud)
这种方式的工作方式是对Accept的第一个虚拟调用解析回特征的具体类型.对Visit方法的调用不是虚拟的 - visitor.Visit(this)调用正确的重载调用,因为此时它知道正在访问的东西的确切静态类型.不保留演员表和类型安全.
添加新访问者类型时,此模式非常好.当元素(在你的情况下的特征)发生变化时会更加痛苦 - 每次添加新元素时,都需要更新IVisitor接口和所有实现.所以要仔细考虑.
正如我所提到的,自该书出版至今已近20年,因此您可以在那里找到大量有关访客模式的分析和改进.这样做有助于您开始继续分析.