Visitor pattern

2023-02-10 C# Visitor Design Patterns Accept Visit

The Visitor design pattern is technique to separate an algorithm from object on which it operates. In essence the visitor allows adding new functions to the family of classes without modifying them. Somehow similar approach can be done with patterns mentioned in previous post.

Let’s define few interfaces first

public interface IActionVisitable
{
    void Accept(IActionVisitor impl);
}

public interface IActionVisitor
{
    void Visit(CloseWithoutHireAction action);
    void Visit(RenameAction action);
    void Visit(SetStartDateAction action);
}

First is used to define an action, second it used to define the visitor, where variants of Visit method is defined for all actions. In my case, the actions are operation on database that store information on hiring. The Action keeps data and provides Accept method that dispatch to the appropriate method in supplied IActionVisitor class

public class CloseWithoutHireAction : IActionVisitable
{
    public string Manager { get; }
    public string Req { get; }

    public CloseWithoutHireAction(string manager, string req)
    {
        Manager = manager;
        Req = req;
    }

    public void Accept(IActionVisitor visit) => visit.Visit(this);
}

The Visitor can look like this

/// <summary>
/// Validates that the actions requested can be done and the initial assumptions
/// are correct.
/// </summary>
public class ValidateActions : IActionVisitor
{
    // ...

    public void Visit(CloseWithoutHireAction action)
    {
        log.Info($"Validating {action.GetType().Name}: {action.Manager} {action.Req}");
        ReqShouldExist(action.Manager, action.Req);
    }

    public void Visit(RenameAction action)
    {
        // ...

    }
}

Usage is pretty simple

List<IActionVisitable> actions = new()
{
    new CloseWithoutHireAction("A manager", "HRD114277"),
    new RenameAction("Another manager", "TBD_00000875", "HRD166032"),
    // ...
};
ValidateActions validator = new ValidateActions(Database);
foreach (var a in actions)
    a.Accept(validator);

if (validator.AllOk)
{
    log.Info("Everything is OK");

    DatabaseActions databaseUpdate = new DatabaseActions(Database);
    foreach (var a in actions)
        a.Accept(databaseUpdate);
}
else
{
    log.Warn("There was a problem, skipping database updates");
}

It all is quite simple, especially since you have your interfaces, the Visual Studio refactoring tools make it quite easy to build new actions. Once they are added into IActionVisitor, refactoring offers adding Visit method to all visitors. They in turn can benefit from shared infrastructure. In my case above it the method ReqShouldExist is used by Visit method for all actions.