Tuesday, November 15, 2016

Creating Dynamic Instances Using Custom Attributes with Constructor Values.

During refactoring of the code. I came across Switch Statement and one of my architect suggested me to use Strategy Pattern to refactor the code.

I did understand the Strategy Pattern and implemented the same. I was a little unhappy with the set of new objects that we were creating in the Context Class of Strategy Pattern.

Every time a  new case is added I had to create a class for the newly added type and add the dictionary value in the Context  class code.

Something struck to my mind and I thought lets use the Reflection Powered by Custom Attributes.

Although Reflection is a powerful tool. We should use it in places where it is really required. (Its like With great power comes great responsibility 😈)

Lets have a quick look at the problem in hand.

   public enum ePassengerTitle
  {
            Mr,
            Mrs,
            Doctor,
   }


    ePassengerTitle title = ePassengerTitle.Doctor;
    switch (title)
    {
                case ePassengerTitle.Mr:
                    // do something
                    break;
                case ePassengerTitle.Mrs:
                    // do something
                    break;
                case ePassengerTitle.Doctor:
                    // do something
                    break;
                default:
                    break;
    }


In the above code there are 3 case conditions and if the cases keep growing the maintainability of the code takes a hit.

Solution to the problem

1.There are multiple ways this problem could be solved. I am going to show how we use custom attributes powered with reflections.

We create different classes for all the "Case" Statements- A little Smell of Strategy Pattern.

An Interface defined.

 public interface IPassengerTitleStrategy
  {
        void DoSomthing(string title);
  }

  [AutoResolve("Mr")]
    public class MrPassengerTitleStrategy : IPassengerTitleStrategy
    {
        public void DoSomthing(string title)
        {
            Console.WriteLine("The Title is" + title);
        }
    }


    [AutoResolve("Mrs")]
    public class MrsPassengerTitleStrategy : IPassengerTitleStrategy
    {
        public void DoSomthing(string title)
        {
            Console.WriteLine("The Title is" + title);
        }
    }

    [AutoResolve("Doctor")]
    public class DoctorPassengerTitleStrategy : IPassengerTitleStrategy
    {
        public void DoSomthing(string title)
        {
            Console.WriteLine("The Title is" + title);
        }
    }

If you closely Observe on each classes we have a Custom Attribute. That Attribute is the Key to the Solution.

Lets Define it now.

 public class AutoResolveAttribute : Attribute
 {
        public AutoResolveAttribute(string name)
        {
            
        }
 }

It has a constructor. Its an important aspect.

Next we need to write Reflection logic which will find you all the classes where the attribute is decorated.

 public static IEnumerable<Type> GetTypesWith<TAttribute>(bool inherit) where TAttribute : System.Attribute
  {
            return from a in AppDomain.CurrentDomain.GetAssemblies()
                   from t in a.GetTypes()
                   where t.IsDefined(typeof(TAttribute), inherit)
                   select t;
   }

So what does the above method do. It searches through all the assemblies in the AppDomain and finds those classes that are decorated with AutoResolve attribute. It is a generic method so you can pass any Attribute of your choice.

Once we get all the classes we need to loop through those classes to find out which class has been decorated with the string value that we are looking for.

   private static string GetAttributeName(string value)
        {
            var getAttribute = GetTypesWith<AutoResolveAttribute>(true);
            foreach (var iAttributeValue in getAttribute)
            {
                var attributeValue = iAttributeValue.CustomAttributes.Select(x =>       x.ConstructorArguments[0].Value).First();
                if (attributeValue.ToString().Contains(value))
                    return iAttributeValue.FullName;
            }
            return string.Empty;
        }


So the above method takes the string as parameter and return the FullName of the assembly.

Once we get the full name of the assembly we now need to get an instance. So we use below code for creating an instance.

   public static object GetInstance(string strFullyQualifiedName)
        {
            Type type = Type.GetType(strFullyQualifiedName);
            if (type != null)
                return Activator.CreateInstance(type);
            return null;
        }

So finally what do we do now.

We use the code. That is so obvious.

  ePassengerTitle title = ePassengerTitle.Doctor;

 var typeCode = GetAttributeName(title.ToString());
 var getInstance = GetInstance(typeCode) as IPassengerTitleStrategy;
 getInstance?.DoSomthing(title.ToString());

 So we get an instance of the specific switch value and you use it ☺☺

Finally what did achieve by doing so.

Pros

1. Eradicated the need to write switch case.
2. Wrote more cleaner more maintainable and scalable code.
3. Code that adheres to  SOLID principles

Con

1.Reflection Code - A powerful tool need to use with care.

Happy Coding 😈😈😈





No comments:

Post a Comment