当代码依赖于两个对象的子类型时,是否存在要处理的设计模式

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.

任何建议,或者我最好只使用我拥有的东西而忽视我的感受?

编辑:填写一些条件,让人们更好地了解正在发生的事情.不要太担心这些例子中的确切代码,我只是把它写在了我的头顶.

Chr*_*res 5

您的问题的一般情况称为双调度 - 您需要根据两个参数的运行时类型调度方法,而不仅仅是一个("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年,因此您可以在那里找到大量有关访客模式的分析和改进.这样做有助于您开始继续分析.