Często odwiedzacz pattern jest rzucany wszędzie tam, gdzie pojawia się potrzeba przejścia po drzewie, ale jakoś nie potrafię zrozumieć jego zalety względem zwykłego przejścia (czy rekursywnego czy iteratywnego) oraz sensownego triku języka lub w ostateczności switcha
z jednej strony fajnie bo pozwala nam zgrabnie dodawać wielu visitorów, ale co gdy tego nie potrzebujemy?
Oczywiście przez przypadek ;) gdy szukałem info nt. visitora to trafiłem na krytykę
http://nice.sourceforge.net/visitor.html
i generalnie wyszły mi takie kody:
Visitor:
public class Document
{
public List<DocumentPart> Parts = new List<DocumentPart>();
public void Accept(IVisitor visitor)
{
foreach (var child in Parts)
{
child.Accept(visitor);
}
}
}
public interface IVisitor
{
void Visit(AddPart addPart);
void Visit(MultiplyPart multiplyPart);
}
public abstract class DocumentPart
{
public List<DocumentPart> Children { get; set; } = new List<DocumentPart>();
public string Text { get; set; }
public abstract void Accept(IVisitor visitor);
}
public class AddPart : DocumentPart
{
public int Value { get; set; } = 15;
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
foreach (var child in Children)
{
child.Accept(visitor);
}
}
}
public class MultiplyPart : DocumentPart
{
public int Value { get; set; } = 35;
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
foreach (var child in Children)
{
child.Accept(visitor);
}
}
}
public class MyVisitor : IVisitor
{
public void Visit(AddPart addPart)
{
Console.WriteLine($"AddPart {addPart.Text + addPart.Value}");
}
public void Visit(MultiplyPart multiplyPart)
{
Console.WriteLine($"MultiplyPart {multiplyPart.Text + multiplyPart.Value}");
}
}
użycie:
public static void Main()
{
var doc = new Document();
var part = new AddPart() { Text = "add" };
part.Children.Add(new MultiplyPart() { Text = "multiply"});
doc.Parts.Add(part);
doc.Accept(new MyVisitor());
}
oraz wersja bez Visitora:
public class Document
{
public List<DocumentPart> Parts = new List<DocumentPart>();
public void Process(DocProcessor docProc)
{
foreach (var child in Parts)
{
docProc.Process(child);
}
}
}
public abstract class DocumentPart
{
public List<DocumentPart> Children { get; set; } = new List<DocumentPart>();
public string Text { get; set; }
}
public class AddPart : DocumentPart
{
public int Value { get; set; } = 15;
}
public class MultiplyPart : DocumentPart
{
public int Value { get; set; } = 35;
}
public class DocProcessor
{
public void Process(DocumentPart node)
{
// tutaj dzieje się cały hack/trick/magic
// odnajdujemy metodę o nazwie 'Visit' która ma taki typ parametru, jak nasz obiekt, a zatem (AddPart | MultiplyPart)
var type = node.GetType();
var method = typeof(DocProcessor).GetMethods().Where(x => x.Name == nameof(Visit) && x.GetParameters()[0].ParameterType == type).First();
method.Invoke(this, new[] { node });
foreach (var child in node.Children)
Process(child);
}
public void Visit(AddPart node)
{
Console.WriteLine($"AddPart {node.Text + node.Value}");
}
public void Visit(MultiplyPart node)
{
Console.WriteLine($"MultiplyPart {node.Text + node.Value}");
}
}
oraz użycie:
public static void Main()
{
var doc = new Document();
var part = new AddPart() { Text = "add" };
part.Children.Add(new MultiplyPart() { Text = "multiply" });
doc.Parts.Add(part);
doc.Process(new DocProcessor());
}
i wydaje mi się, że Visitor w tym przypadku dodaje jedynie dużo boilerplate typu te overridy, interfejs, wymusza aby nasze "obiekty domenowe" miały jakiś Accept oraz chyba zgubiłem gdzie właściwie powinienem dać te pętle od przechodzenia po Childach - Accept czy Visit (co i tak nie jest fajne, bo nadmiarowe), lub inaczej?