In a search for flawless Visitor (design pattern) Part III

Part I | Part II | Part III | Part IV

Type Router

Here is what we have got.

Meet the Type Router please.


    public class TypeRouter<TBase>
    {
        private readonly Dictionary<RuntimeTypeHandle, Action<TBase>> Actions
            = new Dictionary<RuntimeTypeHandle, Action<TBase>>();

        public void SetActionFor<T>(Action<T> action) where T : TBase
        {
            if (null != action)
                Actions[typeof(T).TypeHandle] = baseType => action((T)baseType);
        }

        public void Execute(TBase obj)
        {
            if (null == obj)
                return;

            RuntimeTypeHandle t = Type.GetTypeHandle(obj);
            if (Actions.ContainsKey(t))
                Actions[t](obj);
        }
    }

It is a generic class where generic type parameter is used for setting the base type of polymorphic structure, thus making Type Router to "know" the structure it is going to work with.


Storage

We have our storage device for Methods (actions), named as "Actions" and implemented by generic dictionary where a key is a type of subtype and a value is of type Action<TBase> where we will store a corresponding Method for that subtype.

        private readonly Dictionary<RuntimeTypeHandle, Action<TBase>> Actions
            = new Dictionary<RuntimeTypeHandle, Action<TBase>>();


However, in generic parameter for keys instead of "Type" type we will use type of "RuntimeTypeHandle" to improve performance.

(See thorough examination in "Drilling into .NET Runtime microbenchmarks: 'typeof' optimizations" article by Vance Morrison from 1 Oct 2006)


Mapping subtypes to corresponding methods

The SetActionFor<T> public method is used to map subtypes of polymorphic structure to their designated processing methods as described in Part II of this article.

public void SetActionFor<T>(Action<T> action) where T : TBase
{
    if (null != action)
        Actions[typeof(T).TypeHandle] = baseType => action((T)baseType);
}

We use the generic type parameter here for casting and again to get TypeHandle for the key.


Execution

The Execute(TBase obj) public method is where subtype of a structure "meets" with its designated processing Method and gets executed by it.

public void Execute(TBase obj)
{
    if (null == obj)
        return;

    RuntimeTypeHandle t = Type.GetTypeHandle(obj);
    if (Actions.ContainsKey(t))
        Actions[t](obj);
}

This method is a counterpart of Visitor's "Visit" method. But since correct casting is made within lambda expression in SetActionFor<T>, we do not care here about parameter to be of correct subtype as Visitor's "Visit" method does.

There is no need to make this method as generic as well. The benefit is huge - only one method instead of many overloads of it for every subtype and no dependency on subtypes.


Type Router in action

Let's see how it works.

Below is a slightly modified "Driver" polymorphic structure from Part I.


We have removed "Accept" methods. Since we are going to use Type Router we do not have those dependencies anymore. (Paragraph 2, section "The Goal", Part II).

We have made a couple of big boys ("SchoolBusDriver" & "FireTruckDriver") to implement "IBigVehicleDriver" interface (we will see later why).

public abstract class Driver{}

public interface IBigVehicleDriver 
{
    string ToString();
}

public class SchoolBusDriver : Driver, IBigVehicleDriver
{
    public override string ToString()
    {
        return "School bus driver";
    }

    string IBigVehicleDriver.ToString()
    {
        return this.ToString() + " is a big vehicle driver";
    }
}

public class FireTruckDriver : Driver, IBigVehicleDriver
{
    public override string ToString()
    {
        return "Fire truck driver";
    }

    string IBigVehicleDriver.ToString()
    {
        return this.ToString() + " is a big vehicle driver";
    }
}

public class JeepDriver : Driver
{
    public override string ToString()
    {
        return "Jeep driver";
    }
}
}


Let's have a static class named "Vehicles" to conveniently bind together all drivers' processing methods in one place.

Each processing method for suitable driver's subtype will substitute corresponding vehicle.


    public static class Vehicles
    {
        public static void SchoolBus(SchoolBusDriver driver)
        {
            Console.WriteLine(driver.ToString());
            Console.WriteLine("Has got into School Bus");
            Console.WriteLine();
        }

        public static void FireTruck(FireTruckDriver driver)
        {
            Console.WriteLine(driver.ToString());
            Console.WriteLine("Has got into Fire Truck");
            Console.WriteLine();
        }

        public static void Jeep(JeepDriver driver)
        {
            Console.WriteLine(driver.ToString());
            Console.WriteLine("Has got into Jeep");
            Console.WriteLine();
        }
    }


Note as neither our drivers nor their vehicles have any clue about Type Router!

Let's route every driver to its corresponding vehicle without usage of any control structure or run time type checking.


static void Main(string[] args)
{
    RoutingToMethodsWithSubtypeParameter();


    Console.ReadLine();
}

static void RoutingToMethodsWithSubtypeParameter()
{
    var drivers = new Driver[] { 
        new SchoolBusDriver(), 
        new FireTruckDriver(), 
        new JeepDriver() 
    };

    var router = new TypeRouter<Driver>();

    router.SetActionFor<SchoolBusDriver>(Vehicles.SchoolBus);
    // let's skip FireTruckDriver processing 
    // for the sake of example
    router.SetActionFor<JeepDriver>(Vehicles.Jeep);

    foreach (var dr in drivers)
    {
        router.Execute(dr);
    }
}


Result of running code above shows that out drivers have got into their corresponding vehicles, that is each subtype has been successfully routed to its designated processing method.


More possibilities

The Type Router with its pluggable architecture for processing methods offers even more possibilities.

In Part IV we will discover how to augment Type Router with one more overloaded method to allow routing of subtypes, implementing interface to processing methods with a parameter type of that Interface.

We will also see how to use Type Router with Interface being set as a baseType for routing some interface type to processing methods with parameters of specific subtypes that implement that interface.


Alex Movshovich.

Software Developer.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

© 2012 softomiser

More by this Author


Comments

No comments yet.

    Sign in or sign up and post using a HubPages Network account.

    0 of 8192 characters used
    Post Comment

    No HTML is allowed in comments, but URLs will be hyperlinked. Comments are not for promoting your articles or other sites.


    Click to Rate This Article
    working