Open marshcode opened 11 years ago
Additionally, consider adding a dynamic visitor to the code and integrating it into the Maze. Allowing the program to query the concrete tile objects would free the decorators above from keeping lists of specific tiles (see TreasureMaze? keeping TreasureTile? references in a list). Refer to the attached program to see how the visitor pattern can be implemented in C#;
Visitor Pattern in C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
public class Letter{
public string do_letter()
{
return "specific for Letter";
}
}
public class A : Letter {
public string do_a()
{
return "specific for A";
}
}
public class B : Letter {
public string do_b()
{
return "specific for B";
}
}
public class C : Letter
{
public string do_c()
{
return "specific for C";
}
}
public interface IActionVisitor<in TBase>
where TBase : class
{
/// <summary>
/// Register action on <see cref="T"/>.
/// </summary>
/// <typeparam name="T">Concrete type.</typeparam>
/// <param name="action">Action.</param>
void Register<T>(Action<T> action)
where T : TBase;
/// <summary>
/// Visit concrete type.
/// </summary>
/// <param name="value">Type to visit.</param>
void Visit<T>(T value)
where T : TBase;
}
public static class Visitor
{
/// <summary>
/// Create <see cref="IActionVisitor{TBase}"/>.
/// </summary>
/// <typeparam name="TBase">Base type.</typeparam>
/// <returns>New instance of <see cref="IActionVisitor{TBase}"/>.</returns>
public static IActionVisitor<TBase> For<TBase>(Action<object> default_action=null)
where TBase : class
{
return new ActionVisitor<TBase>(default_action);
}
private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
where TBase : class
{
private readonly Dictionary<Type, dynamic> _repository;
private readonly Action<object> default_action;
private void default_action_f(object o){}
public ActionVisitor(Action<object> default_action=null)
{
this._repository = new Dictionary<Type, dynamic>();
this.default_action = default_action;
}
public void Visit<T>(T value)
where T : TBase
{
Type key = value.GetType();
dynamic action;
action = this.default_action;
if (_repository.ContainsKey(key))
{
action = _repository[key];
}
if (action != null)
{
action((dynamic)value);
}
}
public void Register<T>(Action<T> action)
where T : TBase
{
_repository[typeof(T)] = action;
}
}
}
public class Worker
{
public int num_a_visited = 0;
public int num_b_visited = 0;
public int num_letter_visited = 0;
public int num_unknown_visited = 0;
public Worker()
{
}
public void visit_unknown(object o)
{
Console.WriteLine(String.Format("Unregistered type: {0}", o.GetType().ToString()));
this.num_unknown_visited += 1;
}
public void visit_letter(Letter letter)
{
Console.WriteLine(letter.do_letter());
this.num_letter_visited += 1;
}
public void visit_a(A a)
{
Console.WriteLine(a.do_a());
this.num_a_visited += 1;
}
public void visit_b(B b)
{
Console.WriteLine(b.do_b());
this.num_b_visited += 1;
}
}
class Program
{
static void Main(string[] args)
{
Worker worker = new Worker();
IActionVisitor<Letter> visitor = Visitor.For<Letter>(worker.visit_unknown);
visitor.Register<A>(worker.visit_a);
visitor.Register<B>(worker.visit_b);
visitor.Register<Letter>(worker.visit_letter);
Letter letter = new Letter();
A a = new A();
B b = new B();
C c = new C();
Letter[] letters = new Letter[] { letter, a, b, c };
Console.WriteLine("Calling Visit on Concrete variable types");
visitor.Visit(letter);
visitor.Visit(a);
visitor.Visit(b);
visitor.Visit(c);
Console.WriteLine();
Console.WriteLine("Calling Visit on Abstract/Superclass variable types");
foreach (Letter l in letters)
{
visitor.Visit(l);
}
Console.WriteLine();
Console.WriteLine(String.Format("Letter's visited: {0}", worker.num_letter_visited));
Console.WriteLine(String.Format("A's visited: {0}", worker.num_a_visited));
Console.WriteLine(String.Format("B's visited: {0}", worker.num_b_visited));
Console.WriteLine(String.Format("Unknown's visited: {0}", worker.num_unknown_visited));
}
}
}
Multiple Dispatch in C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace multiple_dispatch
{
public class A
{
}
public class B : A
{
}
public class C : B
{
}
public class D
{
}
public class E : D
{
}
public class Visitor
{
public void Multi(D d, A a) { System.Console.WriteLine("DA"); }
public void Multi(D d, B b) { System.Console.WriteLine("DB"); }
public void Multi(D d, C c) { System.Console.WriteLine("DC"); }
}
public class SubVisitor1 : Visitor
{
public void Multi(E e, A a) { System.Console.WriteLine("EA"); }
public void Multi(E e, B b) { System.Console.WriteLine("EB"); }
public void Multi(E e, C c) { System.Console.WriteLine("EC"); }
}
public class SubVisitor2 : Visitor
{
public void Multi(E e, A a) { System.Console.WriteLine("EA"); }
public void Multi(E e, B b) { System.Console.WriteLine("EB"); }
}
class Program
{
static void do_work(Visitor test)
{
A a = new A();
A b = new B();
A c = new C();
D d = new D();
D e = new E();
Console.WriteLine("Single Dispatch");
test.Multi(d, a);
test.Multi(e, b);
test.Multi(e, c);
Console.WriteLine();
Console.WriteLine("Multiple Dispatch via dynamic keyword.");
test.Multi((dynamic)d, (dynamic)a);
test.Multi((dynamic)e, (dynamic)b);
test.Multi((dynamic)e, (dynamic)c);
Console.WriteLine();
Console.WriteLine("Multiple Dispatch via dynamic keyword in call AND assignment.");
var t = (dynamic)test;
t.Multi((dynamic)d, (dynamic)a);
t.Multi((dynamic)e, (dynamic)b);
t.Multi((dynamic)e, (dynamic)c);
}
static void Main(string[] args)
{
Console.WriteLine("=======SubVisitor1==========");
Visitor subvisitor1 = new SubVisitor1();
Program.do_work(subvisitor1);
Console.WriteLine("=======SubVisitor2==========");
Visitor subvisitor2 = new SubVisitor2();
Program.do_work(subvisitor2);
}
}
}
Consider the other attached file as a different mechanism. This uses built in C# items (instead of doing the dispatching ourselves). It involves the use of the dynamic keyword like the first one but this one utilizes var.
Given that we pass in a Visitor but it is used like a SubVisitor?, it looks like this is another form of down casting.
Several deficiencies exist that should be taken care of.
1 and 2 above should happen regardless of the outcome below.
Originally, this was to make it so TreasureMaze could be a true subclass of Maze. However, when considering future improvements (TrapMaze? MonsterMaze) the idea of MazeHandlers came to mind.
These wouldn't be a defined abstract or interface class. They would be a design pattern used in conjuction with subclassed Tiles, Walls and Characters.
They would add functionality to the maze and track state based on their enhancements without causing a combinatorial explosion.
On the surface, this might be a lazy man's Decorator pattern (we wouldn't have to implement a mostly pass-through interface on the container maze).
Alternately, we could implement an abstract pass-through maze to facilitate the creation of maze decorators. This would implement a true decorator pattern without burdening the programmer with implementing the entire maze signature every time.