Design Patterns — Visitor in .NET

We`ll revise what this pattern is about with some examples in .NET how it makes code more readable and adheres to some of the SOLID principles.

visitor design pattern in .net

What is a Visitor, and what kind of problem does it solve?

The visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.

Assume that you are working on an API endpoint that accepts a set of entities. Each entity belongs to a hierarchy with a common ancestor, and your endpoint should validate these entities by different validation rules.

Here you can apply visitor, and it`ll play perfectly.

Let`s look at the wrong solutions for such tasks before the visitor comes into our code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Resource { }

public class Storage : Resource { }

public class Compute : Resource { }

public class Validator
{
public void Validate(List<Resource> resources)
{
foreach (var resource in resources)
{
if (resource is Storage)
{
Validate(resource as Storage);
}

if (resource is Compute)
{
Validate(resource as Compute);
}

//pattern
//if(resource is Type){
// Validate( resource as Type);
//}

}
}

public void Validate(Storage storage)
{
}

public void Validate(Compute compute)
{
}
}

You see that when we add a new type, we also need to update the validator with a proper if block to validate the new type.

This approach violates the OpenClose principle because the code is not close for modifications with new conditions. In order to make it better, we can use the Visitor pattern here.

Take a moment to read about the OpenClose principle.

Take a look at the new code where we implement visitor design pattern:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public abstract class Resource
{
public abstract void Validate(Validator validator);
}

public class Storage : Resource
{
public override void Validate(Validator validator)
{
validator.Validate(this);
}
}

public class Compute : Resource
{
public override void Validate(Validator validator)
{
validator.Validate(this);
}
}

public class Validator
{
public void Validate(List<Resource> resources)
{
foreach (var resource in resources)
{
resource.Validate(this);
}
}

public void Validate(Storage storage)
{
}

public void Validate(Compute compute)
{
}
}

Now validator relies on resource contracts. It should have a method validate which accepts a validator instance, and each type knows the proper method for self-validation.

In the future, when we add a new type, we don`t have to change something inside the validator. It means that the validator adheres OpenClose principle and can validate new types which any changes.

Conclusion

You get a simple but quite powerful pattern as a visitor to make your code shine. If you didn`t use visitor before in your code, I would suggest you check your existing code, and maybe you can improve the quality of it.

I hope the article was helpful for you. I`ll write about other convenient design patterns soon.