Question

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);
}
Was it helpful?

Solution

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

OTHER TIPS

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.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top