5

I've come to a road block in my application architecture. I've just started using the visitor pattern to execute specific algos on abstract objects of which type I don't know at runtime. My problem is that my algo also depends on the type of a nested abstract type as well.

Let me illustrate what I mean:

I have an abstract DataSource class. From this I implement concerete DataSourceReference and DataSourceExplicit classes. I also have an abstract Report class (the deserialized metadata), from which I implement concrete Report classes ReportTypeA and ReportTypeB. When these objects are created, their DataSource may be any extending DataSource class.

I need both, the actual Report type and DataSource type so I can execute accordingly. I can get the concerte Report type using the visitor pattern, but don't know how to do the same for DataSource afterwards/also.

I can't visit DataSource after visiting Report, because I'd lose the concrete type of Report (as you would have to let it accept the base Report type: Accept(SomeDataSourceVisitor d, MetaReport m) - or overload for every possible Report type, which defeats the purpose of Visitor Pattern. See my problem?

Any ideas? I'd like to stay away from using dynamic, as it wouldn't require developers of new Report types to make sure the dispatcher(visitor) supports the new Report.

Current Code:

public abstract class DataSource
{
}

public class DataSourceReference : DataSource
{
    // reference thing(s)
}

public class DataSourceExplicit : DataSource
{
    // explicit thing(s)
}

public abstract class Report
{
    // some shared Report attribute(s)
    // ...

    public DataSource DataSource { get; set; }

    public abstract FinalReport Execute(IReportExecutionDispatcher d);
}

public class ReportA : Report
{
    // ReportA specific attribute(s)
    // ...

    public override Execute(IReportExecutionDispatcher d)
    {
        d.ExecuteReport(this);
    }
}

public class ReportB : Report
{
    // ReportB specific attribute(s)
    // ...

    public override Execute(IReportExecutionDispatcher d)
    {
        d.ExecuteReport(this);
    }
}

public interface IReportExecutionDispatcher
{
    FinalReport ExecuteReport(ReportA);
    FinalReport ExecuteReport(ReportB);
}
svick
  • 236,525
  • 50
  • 385
  • 514
karczilla
  • 105
  • 2
  • 12
  • Can you give some more details on the algo? Why do you need to know the specific classes? Why polymorphic instance methods on DataSource \ Report are not enough? – Massimiliano Feb 19 '12 at 04:22
  • I need to know the specific report class type because of their report specific attributes. Based on these attributes, you will execute accordingly. We need to know the type of datasource it contains in order to get the dataprovider it references/or so on for type of datasource. – karczilla Feb 19 '12 at 17:05

2 Answers2

3

Also found this while scouring the interwebs. Posting it here for future reference and discussion.

Selective Visitor Pattern

Use the Selective Visitor pattern when

  • you're using a programming language that supports multiple classification (and ideally method overloading).
  • you want to perform different kinds of operations on the (potentially disparate kinds of) elements in an object structure.
  • you want to avoid contaminating the element classes with operations that do not relate to their essential responsibilities.
  • you want to be able to easily add new kinds of elements to the structure without compromising their existing designs.
Community
  • 1
  • 1
karczilla
  • 105
  • 2
  • 12
2

You want to have N * M methods, where N is the count of report types and M is the count of data source type. I think the right approach here is to split them into N types, each with M methods, plus one helper type, that helps us make the first step in dispatching. Something like this:

public interface IDataSourceExecutionDispatcher
{
    FinalReport ExecuteReport(DataSourceExplicit dataSource);
    FinalReport ExecuteReport(DataSourceReference dataSource);
}

public interface IReportExecutionDispatcher
{
    IDataSourceExecutionDispatcher GetDataSourceDispatcher(ReportA report);
    IDataSourceExecutionDispatcher GetDataSourceDispatcher(ReportB report);
}

public class ReportExecutionDispatcher: IReportExecutionDispatcher
{
    public IDataSourceExecutionDispatcher GetDataSourceDispatcher(
         ReportA report)
    {
        return new ReportADataSourceExecutionDispatcher(report);
    }

    public IDataSourceExecutionDispatcher GetDataSourceDispatcher(
         ReportB report)
    {
        return new ReportBDataSourceExecutionDispatcher(report);
    }
}

class ReportADataSourceExecutionDispatcher : IDataSourceExecutionDispatcher
{
    public ReportADataSourceExecutionDispatcher(ReportA report)
    {
        // save the report into a field
    }

    public FinalReport ExecuteReport(DataSourceExplicit dataSource)
    {
        // use saved report A and explicit dataSource here
    }

    public FinalReport ExecuteReport(DataSourceReference dataSource)
    {
        // similar, but with reference dataSource
    }
}

class ReportBDataSourceExecutionDispatcher : IDataSourceExecutionDispatcher
{
     // similar to ReportA dispatcher, except it takes ReportB in constructor
}

Now we just need to modify our data sources to accept the new dispatcher:

public abstract class DataSource
{
    public abstract FinalReport Execute(IDataSourceExecutionDispatcher d);
}

public class DataSourceReference : DataSource
{
    public override FinalReport Execute(IDataSourceExecutionDispatcher d)
    {
        return d.ExecuteReport(this);
    }
}

And also modify reports:

public abstract class Report
{
    public DataSource DataSource { get; set; }

    public abstract FinalReport Execute(IReportExecutionDispatcher d);
}

public class ReportA : Report
{
    public override FinalReport Execute(IReportExecutionDispatcher d)
    {
        var dispatcher = d.GetDataSourceDispatcher(this);
        return DataSource.Execute(dispatcher);
    }
}
svick
  • 236,525
  • 50
  • 385
  • 514
  • well done svick!! I will ultimately need to go one step deeper into the rabbit hole because I will need to know the TYPE of provider the DataSourceReferences is referencing. For example: I have a PivotReport that extends base Report, it contains a DataSourceReference to "provider1", at the point of which I have my PivotReport and know it has a DataSourceReference, I need to also know if the provider is an OlapProvider or a DatabaseProvider so I can generate the correct query (based on the pivot attributes), to send to the provider parse the output and return the Final report. – karczilla Feb 19 '12 at 17:51
  • Question: am I going too far by having PivotReport rather than OlapPivotReport/DBPivotReport? These will contain the same attributes (rows/col/value/filters/source-(table/cube-name)) and making each a separate class is pointless. I could for instance execute my Olap pivot on the database as long as the field and source names are the same for instance - why not? – karczilla Feb 19 '12 at 17:51