Let's say you are designing a system and you have worked out what classes you are going to need; but you also realise that as time goes on you are going to have to release other "collaborator" objects for your system. You would like to release these new objects only, and not all the objects in the system, but how are you going to allow existing objects to handle these new "collaborator" objects, when you have no idea what these new objects will do at the time that you are designing the original objects? Yeah its a bummer isn't it?

Well, luckily there is a pattern to deal with this situation, it's called Double Dispatch. In this pattern the original object does not handle messages from its collaborating objects; instead it dispatches the message back to the collaborator along with a reference to itself. The "double dispatching" of a message allows collaborating objects to be added to a system without affecting the original objects.

Below is an example showing an Account object that has to handle a number of Transaction objects. To allow other Transaction objects to be added to the system, as and when required, without affecting the original Account object; the Account object uses "double dispatch" to dispatch the message back to the Transaction object for processing. Clever eh? :-)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DoubleDispatch
{
    /// <summary>
    /// I am an abstract class from which all transactions derive
    /// </summary>
    public abstract class Transaction
    {
        //GS - Ensure each derived class implements this method
        public abstract void ExecuteWithAccount(Account theAccount);
    }

    /// <summary>
    /// I represent a bank account
    /// </summary>
    public class Account
    {
        public double Balance { get; set; }

        public void ExecuteTransaction(Transaction theTransaction)
        {
            //GS - I have no knowledge of how this transaction
            //works, so dispatch the message back to the caller
            theTransaction.ExecuteWithAccount(this);
        }
    }

    /// <summary>
    /// I am a debit transaction, I debit an account
    /// balance by my value.
    /// </summary>
    public class DebitTransaction : Transaction
    {
        public double Value { get; set; }

        /// <summary>
        /// GS - Handle the double dispatched message
        /// </summary>
        /// <param name="anAccount"></param>
        public override void ExecuteWithAccount(Account anAccount)
        {
            //GS - Show the user the message has been
            //double dispatched
            Console.WriteLine(
                "Message dispatched back to Debit Transaction.");

            anAccount.Balance -= Value;
        }
    }

    /// <summary>
    /// I am a credit transaction, I credit an account
    /// balance by my value.
    /// </summary>
    public class CreditTransaction : Transaction
    {
        public double Value { get; set; }

        /// <summary>
        /// GS - Handle the double dispatched message
        /// </summary>
        /// <param name="anAccount"></param>
        public override void ExecuteWithAccount(Account anAccount)
        {
            //GS - Show the user the message has been
            //double dispatched
            Console.WriteLine(
                "Message dispatched back to Credit Transaction.");

            anAccount.Balance += Value;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //GS - Let's test the pattern

            //GS - Create an account and give it an opening balance
            Account theAccount = new Account();
            theAccount.Balance = 100.00;

            //GS - Show the opening balance
            Console.WriteLine("The opening balance is {0}",
                theAccount.Balance);

            //GS - Create a credit transaction for 50 pounds
            CreditTransaction cr = new CreditTransaction();
            cr.Value = 50.00;

            //GS - Apply the transaction to the account
            Console.WriteLine("Account executing the transaction.");
            theAccount.ExecuteTransaction(cr);

            //GS - Show the new balance
            Console.WriteLine("The new balance is {0}",
                theAccount.Balance);

            //GS - Create a debit transaction for 50 pounds
            DebitTransaction dr = new DebitTransaction();
            dr.Value = 50;

            //GS - Apply the transaction
            Console.WriteLine("Account executing the transaction.");
            theAccount.ExecuteTransaction(dr);

            //GS - Show the new balance
            Console.WriteLine("The new balance is {0}",
                theAccount.Balance);

            //GS - Hang around so the user can see the console
            Console.ReadLine();
        }
    }
}