Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/typescript/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 当代码依赖于两个对象的子类型时,是否需要处理设计模式_C#_Oop_Design Patterns - Fatal编程技术网

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

C# 当代码依赖于两个对象的子类型时,是否需要处理设计模式,c#,oop,design-patterns,C#,Oop,Design Patterns,我会尽量明确,以防有比回答我的问题更好的解决方案 我在C#工作 我有一个报告模板,可以包括任何数量的“功能”打开。功能可能是信息表、饼图/条形图、列表等。我正在以文本文件或PDF格式生成报告(将来可能还有其他选项) 到目前为止,我有一个IFeature界面,以及一些实现它的功能类型:ChartFeature,ListFeature,等等。 我从数据库中读取已启用功能的列表,并将每个功能与数据id一起传递给一个方法,该方法返回一个正确类型的填充的IFeature 我还有一个IReportWrite

我会尽量明确,以防有比回答我的问题更好的解决方案

我在C#工作

我有一个报告模板,可以包括任何数量的“功能”打开。功能可能是信息表、饼图/条形图、列表等。我正在以文本文件或PDF格式生成报告(将来可能还有其他选项)

到目前为止,我有一个
IFeature
界面,以及一些实现它的功能类型:
ChartFeature
ListFeature
,等等。 我从数据库中读取已启用功能的列表,并将每个功能与数据id一起传递给一个方法,该方法返回一个正确类型的填充的
IFeature

我还有一个
IReportWriter
接口,该接口由
textreptwriter
和PdfReportWriter实现。该接口有一个方法:
AddFeature(IFeature)

问题是每个writer中的
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;
}
public void AddFeature(IFeature)
{
InsertSectionBreakIfRequired();
如果(IFeature是TableFeature)
{
TableFeature tf=(TableFeature)特性;
streamWriter.WriteLine(tf.Title);
for(int row=0;row
在PDF编写器中,将修改上述内容以生成PDF表格单元格、文本框等

这感觉很难看。我更喜欢它作为
AddFeature(ListFeature){…}
AddFeature(ChartFeature)
,因为至少在编译时检查了它,但实际上它只是将问题转移到了外部,所以现在如果我调用的IReportWriter
if(feature is…

将显示代码移动到功能中只会逆转问题,因为它需要知道是编写纯文本还是PDF

有什么建议吗,还是我最好只是利用我所拥有的而忽略我的感受

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

我想您正在尝试绘制一些东西(例如,将其输出为pdf或文本或其他格式……)

我的猜测是这样的:

interface IReportWriter {
    void AddFeature(IFeature feature);
    // Some other method to generate the output.
    IOutput Render();
    // Drawing primitives that every report writer implements
    void PrintChar(char c);
    void DrawLine(Point begin, Point end);
    ...
}

// Default implementation for ReportWriters
abstract class AbstractReportWriter {
    private IList<IFeature> features = new List<IFeature>();

    ...

    public void AddFeature(IFeature feature) {
        this.features.Add(feature);
    }

    public IOutput Render() {
        foreach(var feature in this.features) {
            feature.RenderOn(this);
        }
    }

    // Leave the primitives abstract
    public abstract void PrintChar(char c);
    public abstract void DrawLine(Point begin, Point end);
}
public interface IFeatureVisitor {
    void Visit(ChartFeature feature);
    void Visit(ListFeature feature);
    // ... etc one per type of feature
}
var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
    feature.Accept(writer);
}
writer.FinishUp();
下面是
ChartFeature
的一个示例实现:

public class ChartFeature : IFeature {
    ...
    public void RenderOn(IReportWriter report) {
       // Draw the chart based on the primitives.
       report.DrawLine(..., ...);
       ...
    }
}

我会用一种稍微不同的方式来构建它:

我有一个
IReport
对象,它构成了报告中的所有功能。此对象将具有方法
AddFeature(IFeature)
GenerateReport(IReportWriter)

然后,我将使用
IFeature
实现
WriteFeature(IReport,IReportWriter)
,这样就可以将功能的实际处理方式委托给功能本身


您构建代码的方式让我认为,没有办法以任何给定编写器都可以处理的格式无关的方式编写特性,所以让对象本身来处理这个问题

问题的一般情况称为双重分派-您需要根据两个参数的运行时类型分派到方法,而不仅仅是一个参数(“this”指针)

处理这个问题的一个标准模式称为访问者模式。它的描述可以追溯到最初的设计模式书,所以有很多例子和分析

基本的想法是你有两个基本的东西-你有元素(你正在处理的东西)和访客,他们在元素上处理。您需要对这两种方法进行动态调度,因此实际调用的方法根据元素和访问者的具体类型而有所不同

在C#中,有点类似于您的示例,您将定义一个IFeatureVisitor接口,如下所示:

interface IReportWriter {
    void AddFeature(IFeature feature);
    // Some other method to generate the output.
    IOutput Render();
    // Drawing primitives that every report writer implements
    void PrintChar(char c);
    void DrawLine(Point begin, Point end);
    ...
}

// Default implementation for ReportWriters
abstract class AbstractReportWriter {
    private IList<IFeature> features = new List<IFeature>();

    ...

    public void AddFeature(IFeature feature) {
        this.features.Add(feature);
    }

    public IOutput Render() {
        foreach(var feature in this.features) {
            feature.RenderOn(this);
        }
    }

    // Leave the primitives abstract
    public abstract void PrintChar(char c);
    public abstract void DrawLine(Point begin, Point end);
}
public interface IFeatureVisitor {
    void Visit(ChartFeature feature);
    void Visit(ListFeature feature);
    // ... etc one per type of feature
}
var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
    feature.Accept(writer);
}
writer.FinishUp();
然后,在IFeature界面中添加一个“Accept”方法

您的功能实现将实现如下所示的Accept方法:

public class ChartFeature : IFeature {
    public void Accept(IFeatureVisitor visitor) {
        visitor.Visit(this);
    }
}
然后,您的报告编写者将实现IVisitor接口,并在每种类型中执行它应该执行的任何操作

要使用它,它看起来像这样:

interface IReportWriter {
    void AddFeature(IFeature feature);
    // Some other method to generate the output.
    IOutput Render();
    // Drawing primitives that every report writer implements
    void PrintChar(char c);
    void DrawLine(Point begin, Point end);
    ...
}

// Default implementation for ReportWriters
abstract class AbstractReportWriter {
    private IList<IFeature> features = new List<IFeature>();

    ...

    public void AddFeature(IFeature feature) {
        this.features.Add(feature);
    }

    public IOutput Render() {
        foreach(var feature in this.features) {
            feature.RenderOn(this);
        }
    }

    // Leave the primitives abstract
    public abstract void PrintChar(char c);
    public abstract void DrawLine(Point begin, Point end);
}
public interface IFeatureVisitor {
    void Visit(ChartFeature feature);
    void Visit(ListFeature feature);
    // ... etc one per type of feature
}
var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
    feature.Accept(writer);
}
writer.FinishUp();
其工作方式是,接受的第一个虚拟调用解析回特性的具体类型。对Visit方法的调用不是虚拟的-对
visitor.Visit的调用(this)
调用正确的重载,因为此时它知道所访问对象的确切静态类型。没有强制类型转换,并保留类型安全性

当添加新的访问者类型时,这种模式非常好。当元素(在您的案例中的特性)发生变化时,会更加痛苦——每次添加新元素时,都需要更新IVisitor接口和所有实现。所以仔细考虑。


正如我提到的,这本书出版已经将近20年了,所以你可以在那里找到很多关于访问者模式的分析和改进。有益的是,这给了你足够的开始来继续你的分析。

我会避免访问者,原因有二:1)这很复杂