Tuesday, September 27, 2011

Writing an Extensible Application – Part 2: Extensibility by Reflection

 

Hi,

This time I will present a very primitive (yet somehow effective) extensibility method: Extensibility by Reflection. This method is good when you don’t want to over complicate things and all you need is a simple way to extend some specific types. This method is based on the Reflection mechanism of .Net (as the name suggests).

Let’s make a short recap of the previous post. I have made a small application that simulates balls within an enclosed rectangular area. Each ball has three “properties”: The Collider, the Mover, and the Drawer. The UI allows the user to select each of the aforementioned properties from a list and create a ball using these properties. The most important thing I did was to design the application in an extensible way. The main application uses all the data through interfaces which are defined in an external DLL (BallsInterfaces.dll).

I have created an additional project which represents the extensibility client (a dll your user would write). For coherence, I have moved all the basic types to that dll and now all of our colliders, drawers, and movers will be loaded through extensibility. I changed the build target path of the new project to be inside <Main project dir>/Extensibility. In the main project I have added the following key to the application settings:

  1. <appSettings>
  2.   <add key="ExtensibilityPath" value="Extensibility"/>
  3. </appSettings>

From this point things are quite simple. All we have to do is to load all the DLLs from the defined path and in each such DLL find and instanciate any class that inherits from ICollider, IMover, or IDrawer interfaces. The following code does just that:

  1. private void LoadExtensibility()
  2. {
  3.   Assembly applicationAssembly =  Assembly.GetAssembly(GetType());
  4.   string extensibilityPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(applicationAssembly.Location), ConfigurationManager.AppSettings["ExtensibilityPath"]);
  5.   string[] dllFiles = System.IO.Directory.GetFiles(extensibilityPath, "*.dll");
  6.   foreach (string fileName in dllFiles)
  7.   {
  8.     Assembly currentAssembly =  Assembly.LoadFile(fileName);
  9.     Type[] innerTypes = currentAssembly.GetTypes();
  10.     foreach (Type t in innerTypes)
  11.     {
  12.       if (t.IsClass && t.IsPublic)
  13.       {
  14.         if (t.GetInterfaces().Contains(typeof(ICollider)))
  15.         {
  16.           Colliders.Add(new StrategyAdapter(t));
  17.         }
  18.  
  19.         if (t.GetInterfaces().Contains(typeof(IMover)))
  20.         {
  21.           Movers.Add(new StrategyAdapter(t));
  22.         }
  23.  
  24.  
  25.         if (t.GetInterfaces().Contains(typeof(IDrawer)))
  26.         {
  27.           Drawers.Add(new StrategyAdapter(t));
  28.         }
  29.  
  30.       }
  31.     }
  32.   }
  33. }

A few things worth mentioning:

  1. I have added three ObservableCollections named – Colliders, Movers, and Drawers and hooked them up with the ComboBoxes in the application.
  2. Each observable collection contains an adapter class (StrategyAdapter) which mitigates the extensible type with the instance creation. It also provides the Name property for display within the ComboBoxes. The StrategyAdapter code is as follows:
  1. public class StrategyAdapter
  2.   {
  3.     
  4.     protected Type _innerItemType;
  5.     public string Name
  6.     {
  7.       get
  8.       {
  9.         if (_innerItemType != null)
  10.         {
  11.           return _innerItemType.Name;
  12.         }
  13.         return "Null";
  14.  
  15.       }
  16.       
  17.     }
  18.     public StrategyAdapter(Type itemType)
  19.     {
  20.       _innerItemType = itemType;
  21.     }
  22.  
  23.     public T CreateInstance<T>() where T:class
  24.     {
  25.       return Activator.CreateInstance(_innerItemType) as T;
  26.     }
  27.   }

And from this code you may infer that the ball creation code becomes:

  1. Ball ball = new Ball(SelectedMover.CreateInstance<IMover>(), SelectedCollider.CreateInstance<ICollider>(), SelectedDrawer.CreateInstance<IDrawer>(), _environment);

To extend this example I have added three new implementations of the extensibility interfaces:

  1. The Parabolic Mover – It moves the ball as if there was gravity (i.e. with acceleration).
  2. The Blinker Drawer – It creates a slightly bigger red ball that slowly disappears and then instantly reappears.
  3. The Stopper Collider – It stops the other ball on the spot after collision.

As usual, you can download the sources from my SkyDrive -

I want to take this opportunity to say שנה טובה וחג שמח to all my Jewish friends. Nice and fun 国庆节 to all my Chinese friends, rest and spend time with your families. Happy Friday to all my Pastafarian friends (since every Friday is a holyday!).

Thanks for reading,

Boris.

No comments:

Post a Comment