Using Visitor Pattern with reflection in .Net

In my previous visitor pattern post constraints section, I wrote about the ability to mitigate the limitation of the visitor pattern using reflection.

Since then, I have gotten a few emails requesting refactoring the sample application to show how and what should be refactored.

These are the steps I took to refactor the application:

  1. Refactor IVisitor interface to the following:

    public interface IVisitor
    {
        void Visit(object @object);
    }
  2. Move the visit method from USDVisitor to VisitorBase class and add some reflection logic to read and write the IVisitor's properties (addition is highlighted in yellow)

    public abstract class VisitorBase : IVisitor
      {
          public Dictionary<string, decimal> Converted { get; set; }
          public virtual string FromCurrency { get; set; }
          public virtual string ToCurrency { get; set; }
          public string ConversionServiceURL
          {
              get
              {
                  return string.Format("http://finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s={0}{1}=X",
                      FromCurrency.ToUpper(), ToCurrency.ToUpper());
              }
              set
              {
                  ConversionServiceURL = value;
              }
          }
          protected VisitorBase()
          {
              if (Converted == null)
              {
                  Converted = new Dictionary<string, decimal>();
              }
          }
          public virtual async Task GetCurrencyExchangeAsync(decimal exchange, Action<decimal> action)
          {
              try
              {
                  decimal converted = 0.0m;
                  var client = new WebClient();
                  string result = await client.DownloadStringTaskAsync(ConversionServiceURL);
                  var split = result.Split(',');
                  if ( split.Length >= 0)
                  {
                      decimal rate;
                      if (Decimal.TryParse(split[1], out rate))
                      {
                          converted = rate * exchange;
                      }
                  }
                  action(converted);
              }
              catch (WebException ex)
              {
                  Console.Write(ex.Message);
              }
          }
          private PropertyInfo GetProperty(object @object, string propertyName)
          {
              var props = @object.GetType().GetProperties();
              return   props.FirstOrDefault(x => x.Name == propertyName);
          }
          public async void Visit(object @object)
          {
              FromCurrency = (string) GetProperty(@object, "PurchaseCurrency").GetValue(@object);
              var totalPurchaseValue = (decimal)GetProperty(@object, "TotalPurchaseValue").
    GetValue(@object); await GetCurrencyExchangeAsync((decimal)totalPurchaseValue, ConvertedValue => { var totalpurchaseConvertedProp = GetProperty(@object, "TotalPurchaseConverted"); totalpurchaseConvertedProp. SetValue(@object, Convert.ChangeType(totalPurchaseValue,
    totalpurchaseConvertedProp.PropertyType)); Converted.Add(@object.GetType().Name, ConvertedValue); }).ConfigureAwait(false); } }
  3. This is the USDVisitor after refactoring:

    public class USDVisitor : VisitorBase
    {
        public override string ToCurrency
        {
            get
            {
                return "USD";
            }
            set
            {
                base.ToCurrency = value;
            }
        }
    }

The below UML diagram models our sample application after refactoring.


By using reflection we can now add as many visitors to the application without having to add additional visit methods for to handle them.

Download the refactored app (2.1MB)

If you have any comments or need further clarification, hit the comment box and I will respond as soon as I can.

Pingbacks and trackbacks (1)+

Add comment