Is there a design pattern for processing when the code depends on a subtype of two objects

I will try to be as explicit as possible, in case there is a better solution to my problem than the answer to my question.

I work in C #.

I have a report template that can include any number of “functions”. A feature may be an information table, pie / bar chart, list, etc. I will generate the report as a text file or PDF (possibly other options in the future).

Until now, I have an interface IFeature, and some implementing its functions ChartFeature, ListFeatureetc. I read the list of functions included from the database and passed them to the method along with the data identifier, and the method returns the filled one of the IFeaturedesired type.

I also have an interface IReportWriterthat implements TextReportWriterPdfReportWriter. This interface has a method: AddFeature(IFeature).

The problem is what AddFeatureeach writer looks like:

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;
}

In a PDF writer, the above will be modified to create PDF table cells, text fields, etc.

It seems ugly. I like it a little better than AddFeature(ListFeature){...}, AddFeature(ChartFeature)because at least then it compiles the time, but in practice it just moves the problem, so now it's outside if I call IReportWriter if(feature is ...).

Moving the display code into a function simply eliminates the problem, because it will need to know if it should write plain text or PDF.

Any suggestions, or is it best for me to use what I have and ignore my feelings?

: , , . , .

+4
4

double-dispatch - , , ( "this").

. Design Patterns, .

, : ( ) , . - .

# sorta sorta IFeatureVisitor :

public interface IFeatureVisitor {
    void Visit(ChartFeature feature);
    void Visit(ListFeature feature);
    // ... etc one per type of feature
}

IFeature "".

public interface IFeature {
    public void Accept(IFeatureVisitor visitor);
}

Accept :

public class ChartFeature : IFeature {
    public void Accept(IFeatureVisitor visitor) {
        visitor.Visit(this);
    }
}

IVisitor , .

, :

var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
    feature.Accept(writer);
}
writer.FinishUp();

, Accept . - visitor.Visit(this) , , . .

. , ( ) - , , IVisitor . .

, 20 , . .

+4

:

IReport, . AddFeature(IFeature) GenerateReport(IReportWriter)

IFeature WriteFeature(IReport, IReportWriter) , Feature.

, , , , , .

+1

, - (.. 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);
}

:

interface IFeature {
    void RenderOn(IReportWriter);
}

ChartFeature:

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

Visitor : 1) 2) , IFeature IReportWriter . Visitor Element. . @ fooobar.com/questions/1604794/.... .

UML:

UML class diagram of OP's code

AddFeature . , , , .

Replace conditional with polymorphism, IFeature.WriteOutput(), . IReportWriter

public void AddFeature(IFeature feature)
{
    InsertSectionBreakIfNeeded();
    feature.WriteOutput();
    sectionBreakNeeded = true;
}

, IFeature Strategy IReportWriter :

Strategy pattern in UML


Factory

Abstract Factory with WriteMethod () polymorphic call

, [PDF, Text] [Chart, List].

/ PdfReportFeature ListReportFeature , , , . .

, , PdfListFeature, WriteOutput, , . ReportWriter feature.WriteOutput() , () .

There is no double submission, since you will not mix PDF and text reports together (the visitor really does not make sense to me). When you create a report, this or that type. Your abstract Factory template will help you create and pass on a suitable class for the chart or list in the record.

I updated the above part of the strategy to fit Factory's abstract approach. Hope this makes sense.

0
source

All Articles