2
\$\begingroup\$

Programming to an interface is a design principle that enhances the flexibility of software by hiding implementation details. Consider an object tree that only exposes an interface, but you want to add additional behaviour that operates on implementation details. A compositional object structure like that exposes an interface that's applicable to all entities in the object structure, so in many cases it will not expose all implementation details of all implementing types. However there are situations where you need implementation details of the objects implementing those interfaces, for example a deep copy where you need all object fields.

So that's exactly what I achieved using the visitor pattern. I don't like using automapper, because I like to be able to find all usages of code and automapper introduces reflection to map classes.

I'm mapping some postmodels that I receive through a (C# ASP.NET Core) REST API.

Compositional postmodels

I'm mapping those objects to the domain entities:

Compositional domain models

My visitor looks like this:

/// <summary>
///  Visitor Pattern: Concrete Visitor
/// </summary>
internal class MapInputPostModelsToInputElementsVisitor : IMapInputPostModelsToInputElementsVisitor
{
    private readonly IInputElementFactory inputElementFactory;

    private readonly Stack<IInputElement> inputElements = new Stack<IInputElement>();

    public MapInputPostModelsToInputElementsVisitor(IInputElementFactory inputElementFactory)
    {
        this.inputElementFactory = inputElementFactory;
    }

    public IEnumerable<IInputElement> Get()
    {
        return inputElements;
    }

    public IInputElement Visit(InvoerBundelPostModel invoerBundelPostModel)
    {
        var Invoerberichten = invoerBundelPostModel.InvoerBerichten
            .Select(b => b.Accept(this))
            .Cast<InvoerBericht>();
        var inputElement = inputElementFactory.CreateInvoerBundel(invoerBundelPostModel, Invoerberichten);
        inputElements.Push(inputElement);
        return inputElement;
    }

    public IInputElement Visit(InvoerBerichtPostModel invoerBerichtPostModel)
    {
        var invoerRegels = invoerBerichtPostModel.InvoerRegels
            .Select(r => r.Accept(this))
            .Cast<InvoerRegel>();
        var invoerzorgactiviteiten = invoerBerichtPostModel.InvoerZorgactiviteiten
            .Select(r => r.Accept(this))
            .Cast<InvoerZorgactiviteit>();
        var invoerTijdsbestedingen = invoerBerichtPostModel.InvoerTijdsbestedingen
            .Select(r => r.Accept(this))
            .Cast<InvoerTijdsbesteding>();

        var inputElement = inputElementFactory.CreateInvoerBericht(invoerBerichtPostModel, 
            invoerRegels, invoerzorgactiviteiten, invoerTijdsbestedingen);
        inputElements.Push(inputElement);
        return inputElement;
    }

    public IInputElement Visit(InvoerRegelPostModel invoerRegelPostModel)
    {
        var inputElement = inputElementFactory.CreateInvoerRegel(invoerRegelPostModel);
        inputElements.Push(inputElement);
        return inputElement;
    }

    public IInputElement Visit(InvoerTijdsbestedingPostModel invoerTijdsbestedingPostModel)
    {
        var inputElement = inputElementFactory.CreateTijdsbesteding(invoerTijdsbestedingPostModel);
        inputElements.Push(inputElement);
        return inputElement;
    }

    public IInputElement Visit(InvoerZorgactiviteitPostModel invoerZorgactiviteitPostModel)
    {
        var inputElement = inputElementFactory.CreateZorgactiviteit(invoerZorgactiviteitPostModel);
        inputElements.Push(inputElement);
        return inputElement;
    }
}

The factory's interface looks like this:

public interface IInputElementFactory
{
    IInputElement CreateInvoerBundel(InvoerBundelPostModel invoerBundelPostModel, IEnumerable<InvoerBericht> invoerberichten);
    IInputElement CreateInvoerBericht(InvoerBerichtPostModel invoerBerichtPostModel,
        IEnumerable<InvoerRegel> invoerRegels, IEnumerable<InvoerZorgactiviteit> invoerZorgactiviteiten,
        IEnumerable<InvoerTijdsbesteding> InvoerTijdsbestedingen);
    IInputElement CreateInvoerRegel(InvoerRegelPostModel invoerRegelPostModel);
    IInputElement CreateZorgactiviteit(InvoerZorgactiviteitPostModel invoerZorgactiviteitPostModel);
    IInputElement CreateTijdsbesteding(InvoerTijdsbestedingPostModel invoerTijdsbestedingPostModel);
}

A few things raise questions for me:

  • For starters, are there any know alternatives for mapping abstract object trees to similar object trees?
  • Does casting inside the visit methods break the "program to an interface" design rule?
  • Should I iterate children entities inside the visitor or accept method? And if not, what pattern should I use to iterate through all objects in the compositional object tree?
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.