Saturday, July 16, 2011

Factories in C#-Basic, Cached, and Dynamic

Introduction

It seems that one of the more common operations in modern object orientated languages is creating new objects.  In most languages, this is accomplished by using the “new” operator…this is nothing new for most of us out there who’ve been doing OO programming for any length of time.  For those who haven’t been, here’s the general idea – we have a class we want to create and use (let’s say a “Dog”) in our main program.  Here’s the Dog class:

Dog.cs
  1. public class Dog
  2. {
  3.     protected string AnimalType { get; private set; }
  4.  
  5.     public Dog()
  6.     {
  7.         AnimalType="Dog";
  8.     }
  9.  
  10.     public void IntroduceSelf()
  11.     {
  12.         System.Console.WriteLine(IntroductionLine);
  13.         System.Console.WriteLine(Speak);
  14.         System.Console.WriteLine();
  15.     }
  16.  
  17.     protected string IntroductionLine
  18.     {
  19.         get
  20.         {
  21.             string introLine = "Hello, I am a " + AnimalType;
  22.             return introLine;
  23.         }
  24.     }
  25.  
  26.     protected string Speak
  27.     {
  28.         get
  29.         {
  30.             return "Woof";
  31.         }
  32.     }
  33. }

And to create a new Dog we do this in our main program:

Program.cs
  1. class Program
  2. {
  3.     static void Main(string[] args)
  4.     {
  5.         Dog dog = new Dog();
  6.         dog.IntroduceSelf();
  7.  
  8.         System.Console.WriteLine("Press <enter> to end");
  9.         Console.ReadLine();
  10.     }
  11. }

Ok, simple enough, right?

Well, let’s say the requirements expand and we now need to create a cat and a dog.  [ok, now, you ol fogies – I know you already know this…just skim down if you’re bored].  So, we create a new class named Cat that does pretty much the same thing as the Dog class did, with obvious exceptions.  [At this point, if you’re an OO programmer, alarm bells should be going off in your head – Cat class does pretty much the same thing as the Dog class].

So, here’s the Cat class -

Cat.cs
  1. public class Cat
  2.     {
  3.         protected string AnimalType { get; private set; }
  4.  
  5.         public Cat()
  6.         {
  7.             AnimalType = "Cat";
  8.         }
  9.  
  10.         public void IntroduceSelf()
  11.         {
  12.             System.Console.WriteLine(IntroductionLine);
  13.             System.Console.WriteLine(Speak);
  14.             System.Console.WriteLine();
  15.         }
  16.  
  17.         protected string IntroductionLine
  18.         {
  19.             get
  20.             {
  21.                 string introLine = "Hello, I am a " + AnimalType;
  22.                 return introLine;
  23.             }
  24.         }
  25.  
  26.         protected string Speak
  27.         {
  28.             get
  29.             {
  30.                 return "Meow";
  31.             }
  32.         }
  33.     }

And our main program to create a dog and cat looks like this:

Program.cs
  1. class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             Dog dog = new Dog();
  6.             dog.IntroduceSelf();
  7.  
  8.             Cat cat = new Cat();
  9.             cat.IntroduceSelf();
  10.  
  11.             System.Console.WriteLine("Press <enter> to end");
  12.             Console.ReadLine();
  13.         }
  14.     }

Ok, not bad…and we solved the requirements…but, look at all that duplicated code…what if the requirements change again (and, believe me, they will)?  What if we added 50 animal types and then we found a bug that was common in all that code…50 classes would have to change to fix that bug.  What’s the answer?  Using the OO concept of inheritance.

Brief Overview of Inheritance (as it pertains to Factories)

One of the key features of inheritance is the ability to reuse code from a class higher up the inheritance chain (such as a parent or grandparent [or from a base class in C# terms]).  This reused code can be in the form of functions/procedures, data members, or properties (I’m specifically referring to class inheritance here, not inheritance via interfaces).  So, what does this have to do with factories and creation of objects – inheritance is a prerequisite to getting a factory to work for object creation.  So, let’s go ahead and use inheritance and get rid of some of this duplicated code…and, while we’re at it, let’s create a new animal class for a pig.

We’ll start with an abstract base class.  We really don’t want to be creating an animal by itself and there are some things the animals themselves will know how to do that this base animal class won’t (i.e. how to speak).

AbstractAnimalBase.cs
  1. public abstract class AbstractAnimalBase
  2.     {
  3.         protected string AnimalType { get; private set; }
  4.  
  5.         public AbstractAnimalBase(string type)
  6.         {
  7.             AnimalType = type;
  8.         }
  9.  
  10.         public void IntroduceSelf()
  11.         {
  12.             System.Console.WriteLine(IntroductionLine);
  13.             System.Console.WriteLine(Speak);
  14.             System.Console.WriteLine();
  15.         }
  16.  
  17.         protected virtual string IntroductionLine
  18.         {
  19.             get
  20.             {
  21.                 string introLine = "Hello, I am a " + AnimalType;
  22.                 return introLine;
  23.             }
  24.         }
  25.  
  26.         protected abstract string Speak
  27.         {
  28.             get;
  29.         }
  30.     }

Ok, now for the dog, cat, and pig classes that will use this base class.  Take a look at how much shorter these are than the original version that didn’t use inheritance.

Dog.cs
  1. public class Dog : AbstractAnimalBase
  2.     {
  3.         public Dog()
  4.             : base("Dog")
  5.         {
  6.         }
  7.  
  8.         protected override string Speak
  9.         {
  10.             get { return "Woof"; }
  11.         }
  12.     }

 

Cat.cs
  1. public class Cat : AbstractAnimalBase
  2.     {
  3.         public Cat()
  4.             : base("Cat")
  5.         {
  6.         }
  7.  
  8.         protected override string Speak
  9.         {
  10.             get { return "Meow"; }
  11.         }
  12.     }

 

Pig.cs
  1. public class Pig : AbstractAnimalBase
  2.     {
  3.         public Pig()
  4.             : base("Pig")
  5.         {
  6.         }
  7.  
  8.         protected override string Speak
  9.         {
  10.             get { return "Oink"; }
  11.         }
  12.     }

Ok, now to the meat of this post.

Introduction to Factories

So, what is a factory anyway and what can it do for us?  In layman terms, a factory is a piece of code that returns back a specific object from a given input.  Ok, so what’s so special about that, right? – new does that for me.  That’s correct, but, what if you don’t know what type of object you want while you’re coding?  That’s where the factory comes in – you tell the factory what type of object you want and it will return the object needed for that type to you.  In more programmer speak, a factory is one of the Gang of Four design patterns that is used to create objects without having to specify the exact class that will be created.

Yes, I know, you’re the programmer and you will always know what object you need and when - will you though?  Let’s say you’re reading a config file and wanting to do certain operations or load certain values based on the config – do you always know what and how it will be configured?  Or, let’s say you’re reading a database and you’ll have to load data from different tables depending on what the value of a specific field is…or, you’re looking at the file system for images and you come across a JPG and the next image is a GIF.  Each of these is a good candidate for a factory.

Typically, a factory is a static class and/or a static method.  Having the factory class static allows access to it without having to create an instance of the factory class.

Basic Factories

Factories using Strings

The factory method will generally return the common base class or common interface of the objects that could be created by the method…normally the structure is simple, either in a if-else or in a switch statement.  So, for the animal factory, we’d have something like the following code:

AnimalFactory.cs
  1. public static class AnimalFactory
  2.     {
  3.         public static AbstractAnimalBase CreateAnimal(string type)
  4.         {
  5.             type = type.ToLower().Trim();
  6.             switch (type)
  7.             {
  8.                 case "dog":
  9.                     return new Dog();
  10.                 case "cat":
  11.                     return new Cat();
  12.                 case "pig":
  13.                     return new Pig();
  14.                 default:
  15.                     throw new Exception(String.Format("Unknown animal type {0}", type));
  16.             }
  17.         }
  18.     }

Factories using Enumerations

A complaint with the above code is that it’s using strings to do the matching, which some claim is inefficient (due partly to the to lower and trim calls) and error prone (if you misspell, it’ll fail).  This can easily be changed to use an enumeration, which will gain some speed (no need for string manipulation) and is less error prone but adds a little bit more maintenance to the code – the programmer has to update the enum as well as the factory code.  Here’s the same code but using an enumeration instead:

AnimalFactory.cs
  1. public static class AnimalFactory
  2.     {
  3.         public enum AnimalEnum
  4.         {
  5.             Dog,
  6.             Cat,
  7.             Pig
  8.         };
  9.  
  10.         public static AbstractAnimalBase CreateAnimal(AnimalEnum type)
  11.         {
  12.             switch (type)
  13.             {
  14.                 case AnimalEnum.Dog :
  15.                     return new Dog();
  16.                 case AnimalEnum.Cat:
  17.                     return new Cat();
  18.                 case AnimalEnum.Pig:
  19.                     return new Pig();
  20.                 default:
  21.                     throw new Exception(String.Format("Unknown animal type {0}", type));
  22.             }
  23.         }
  24.     }

Well, that’s it for the factory, right?  Well, in the general case, yes…but, there are a couple specific instances that I’ve found a use for lately that someone else may be interested in.

Cached Factories

There are times when creating a class is a long running process and you want to cache this data so you don’t kill your performance, but, the classes that do this all inherit from a base class so they are candidates for a factory pattern.  The sample factories above would create a new animal each time that type is requested – which could run a long-running process.  The cached factory helps with this situation…one thing to note with the cached factory it will return back the same object instance for a specific given type (not that this isn’t necessarily a singleton instance of a class).  So, for example, using the animal example above, the program requests a dog for the first time, a dog (#1) is created and returned back to the caller…on the second call to get a dog, dog (#1) is still returned to the caller…let’s say the code is slightly different than above and we return back a dog for an input of “mut”…with this situation, the program passes in “mut” and we get back a different instance of a dog (#2).  So, don’t get cached returns confused with singletons.

Now, for the code for a cached factory.  Pretty similar to the others but it uses a dictionary to keep hold the type and the returned value.  The code could easily be expanded to allow removal of cached values, etc, if needed.

AnimalFactory.cs
  1. using System.Collections.Generic;
  2.     public static class AnimalFactory
  3.     {
  4.         private static Dictionary<string, AbstractAnimalBase> AnimalCache { get; set; }
  5.  
  6.         public static AbstractAnimalBase CreateAnimalCached(string type)
  7.         {
  8.             if (AnimalCache == null)
  9.             {
  10.                 AnimalCache = new Dictionary<string, AbstractAnimalBase>();
  11.             }
  12.  
  13.             type = type.ToLower().Trim();
  14.  
  15.             if (!AnimalCache.ContainsKey(type))
  16.             {
  17.                 AbstractAnimalBase animal;
  18.                 switch (type)
  19.                 {
  20.                     case "dog":
  21.                         animal = new Dog();
  22.                         break;
  23.                     case "cat":
  24.                         animal = new Cat();
  25.                         break;
  26.                     case "pig":
  27.                         animal = new Pig();
  28.                         break;
  29.                     default:
  30.                         throw new Exception(String.Format("Unknown animal type {0}", type));
  31.                 }
  32.                 AnimalCache.Add(type,animal);
  33.             }
  34.             return AnimalCache[type];
  35.         }
  36.     }

Alternate “Factory” (Cache)

An alternate version of the above factory eliminates the switch statement – so, each animal object will be created and cached…but, each object must manually be created outside the factory (hmm, kinda longer a factory if the object is created elsewhere, is it?) – but that add could be technically done from anywhere, according to the permission of the function/class.  So, the main method could add to the factory and other classes within the application use the factory with what was loaded from main.

AnimalFactory.cs
  1. using System.Collections.Generic;
  2.     public static class AnimalFactory
  3.     {
  4.         private static Dictionary<string, AbstractAnimalBase> AnimalCache { get; set; }
  5.  
  6.         internal void AddAnimalToCache(string type, AbstractAnimalBase animal)
  7.         {
  8.             if (AnimalCache == null)
  9.             {
  10.                 AnimalCache = new Dictionary<string, AbstractAnimalBase>();
  11.             }
  12.  
  13.             type = type.ToLower().Trim();
  14.  
  15.             if (!AnimalCache.ContainsKey(type))
  16.             {
  17.                 AnimalCache.Add(type, animal);
  18.             }
  19.             else
  20.             {
  21.                 throw new Exception(String.Format("Animal type {0} already exists", type));
  22.             }
  23.         }
  24.  
  25.         public static AbstractAnimalBase GetAnimalFromCache(string type)
  26.         {
  27.             if (AnimalCache == null)
  28.             {
  29.                 AnimalCache = new Dictionary<string, AbstractAnimalBase>();
  30.             }
  31.  
  32.             type = type.ToLower().Trim();
  33.  
  34.             if (!AnimalCache.ContainsKey(type))
  35.             {
  36.                 throw new Exception(String.Format("Unknown animal type {0}", type));
  37.             }
  38.  
  39.             return AnimalCache[type];
  40.         }
  41.     }

Dynamic Factories

One thing that has always bothered me about the factory is having to add a class for what I want and then having to also modify the factory and maybe even an enum.  There has to be a way for classes to “self register” themselves with a factory…well, there is with the above code – the constructor of each subclassed animal can add itself to the factory.  Well, that’s great…but who is going to call the constructor for each of those sub-classes?  Too bad the base class can’t determine it’s subclasses…or, wait, it can, but indirectly – through reflection.

Couple words on reflection and creating a factory in this way – reflection is “slow” in general, so you’ll probably want to cache the objects that are created and you probably only want to discover your classes once – on start up or on first use of the factory.  Also, with this method, I’ve only used no-parameter constructors…and all the classes match that no-parameter constructor rule – probably ways around it but it will be a bit more coding to deal with that (i.e. it’s not as easy to do as with a normal factory).  And, while this may be a way to dynamically discover classes for your factory, it raises some security concerns as well so code accordingly.  So, use at your own risk basically.

Ok, enough with the legal warnings and notices.  On to the code…

Brief Introduction to Reflection (as it pertains to Dynamic Factories)

So, the first question is how do we find out what classes are within a file?  .Net actually makes this fairly simple…we have to load the assembly (i.e. dll or exe) into memory, then we can get all the types within that file.  Essentially the code to do this is:

Code Snippet
  1. Assembly assembly = Assembly.LoadFrom(filename);
  2. Type[] types = assembly.GetTypes();

Where filename is the full path to an exe or dll that we want to inspect.

Discovering Interested Types

Next, we just iterate over all the types to see if they match what we’re looking for.  We’ve got to be careful here as a type could be an interface, an abstract class, or not even a class that we’re interested in.  For this, I’ve written a routine to verify a type is what I’m interested in.

Code Snippet
  1. protected static bool IsValidAnimal(Type type, ref AbstractAnimalBase animal)
  2. {
  3.     //is it a sub-class
  4.     if (type.BaseType != null)
  5.     {
  6.         //make sure it's not an interface
  7.         if (!type.IsInterface)
  8.         {
  9.             //is it a sub-class of what we're interested in
  10.             if (type.IsSubclassOf((typeof(AbstractAnimalBase))))
  11.             {
  12.                 //make sure the type is a concrete type (i.e. not abstract)
  13.                 if (!type.IsAbstract)
  14.                 {
  15.                     try
  16.                     {
  17.                         animal = (AbstractAnimalBase)type.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { });
  18.                         return true;
  19.                     }
  20.                     catch (Exception ex)
  21.                     {
  22.                         throw new Exception(String.Format("Failed to load {0} as a add on command from {1}", type.Name, type.AssemblyQualifiedName), ex);
  23.                     }
  24.                 }
  25.             }
  26.         }
  27.     }
  28.     return false;
  29. }

So, to put the loading all together into a single class…

AnimalLoader.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4.  
  5. public class AnimalLoader
  6. {
  7.     public static List<AbstractAnimalBase> LoadAnimals(string filename)
  8.     {
  9.         List<AbstractAnimalBase> animals = new List<AbstractAnimalBase>();
  10.         Assembly assembly = Assembly.LoadFrom(filename);
  11.         Type[] types = assembly.GetTypes();
  12.  
  13.         foreach (Type type in types)
  14.         {
  15.             AbstractAnimalBase animal = null;
  16.             if (IsValidAnimal(type,ref animal))
  17.             {
  18.                 animals.Add(animal);
  19.             }
  20.         }
  21.         return animals;
  22.     }
  23.  
  24.     protected static bool IsValidAnimal(Type type, ref AbstractAnimalBase animal)
  25.     {
  26.         //is it a sub-class
  27.         if (type.BaseType != null)
  28.         {
  29.             //make sure it's not an interface
  30.             if (!type.IsInterface)
  31.             {
  32.                 //is it a sub-class of what we're interested in
  33.                 if (type.IsSubclassOf((typeof(AbstractAnimalBase))))
  34.                 {
  35.                     //make sure the type is a concrete type (i.e. not abstract)
  36.                     if (!type.IsAbstract)
  37.                     {
  38.                         try
  39.                         {
  40.                             animal = (AbstractAnimalBase)type.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { });
  41.                             return true;
  42.                         }
  43.                         catch (Exception ex)
  44.                         {
  45.                             throw new Exception(String.Format("Failed to load {0} as a add on command from {1}", type.Name, type.AssemblyQualifiedName), ex);
  46.                         }
  47.                     }
  48.                 }
  49.             }
  50.         }
  51.         return false;
  52.     }
  53. }

Ok, so we can now load the animals that’re in a dll or exe, but how do we load them into our factory?  Need a key for each of the animals and also a way to load them before the factory is first used.

Making the Abstract Class “Keyable”

Well, we actually have a key in the AbstractAnimalBase class but it’s protected – let’s fix that and make it public so that the factory can access the keys (yes, it can be marked as internal instead as long as the factory and animal base class are in the same exe or dll).  So the new AbstractAnimalBase class is changed to:

AbstractAnimalBase.cs
  1. public abstract class AbstractAnimalBase
  2. {
  3.     public string AnimalType { get; private set; }
  4.  
  5.     public AbstractAnimalBase(string type)
  6.     {
  7.         AnimalType = type;
  8.     }
  9.  
  10.     public void IntroduceSelf()
  11.     {
  12.         System.Console.WriteLine(IntroductionLine);
  13.         System.Console.WriteLine(Speak);
  14.         System.Console.WriteLine();
  15.     }
  16.  
  17.     protected virtual string IntroductionLine
  18.     {
  19.         get
  20.         {
  21.             string introLine = "Hello, I am a " + AnimalType;
  22.             return introLine;
  23.         }
  24.     }
  25.  
  26.     protected abstract string Speak
  27.     {
  28.         get;
  29.     }
  30. }

Dynamic Factory Class

Now, to get that factory working.  What would be nice would be for the factory to load the add ins when it’s first used…well, it can – by using a static constructor.

AnimalFactory.cs
  1. public static class AnimalFactory
  2. {
  3.     private static Dictionary<string, AbstractAnimalBase> Animals { get; set; }
  4.  
  5.     static AnimalFactory()
  6.     {
  7.         Animals = new Dictionary<string, AbstractAnimalBase>();
  8.  
  9.         List<AbstractAnimalBase> animals;
  10.         
  11.         string filename="Animals.dll";
  12.         string path=Assembly.GetExecutingAssembly().Location;
  13.         path=System.IO.Path.GetDirectoryName(path);
  14.         string fileToLoad=System.IO.Path.Combine(path,filename);
  15.  
  16.         try
  17.         {
  18.             animals = AnimalLoader.LoadAnimals(fileToLoad);
  19.  
  20.             foreach (AbstractAnimalBase animal in animals)
  21.             {
  22.                 string animalType = animal.AnimalType.ToLower().Trim();
  23.                 if (!Animals.ContainsKey(animalType))
  24.                 {
  25.                     Animals.Add(animalType, animal);
  26.                 }
  27.                 else
  28.                 {
  29.                     throw new Exception(String.Format("Duplicate animal type {0} found", animalType));
  30.                 }
  31.             }
  32.         }
  33.         catch (Exception ex)
  34.         {
  35.             throw new Exception(String.Format("There was a problem loading the assembly {0}", fileToLoad),ex);
  36.         }
  37.     }

For this factory, for demo purposes, I’ve set the filename to be a static dll named Animals.dll, which the static constructor will use to pass to the AnimalLoader – you’ll more than likely want to use something different, but, remember, static constructors cannot accept parameters so you’ll have to find another way or just loop through all dlls.  Also, this uses the current executing path – change as needed for your own purposes.  Really, not a lot here – animals are discovered and loaded by the AnimalLoader method, then the animal types and the animal are added to the static list of animals for the factory.  If you’ve been paying attention, yes, this is a cached factory at this point as reflection is slow.

Now, for getting the animals out of the factory.

AnimalFactory.cs
  1. public static AbstractAnimalBase GetCachedAnimal(string type)
  2. {
  3.     type=type.ToLower().Trim();
  4.     if (Animals.ContainsKey(type))
  5.     {
  6.         return Animals[type];
  7.     }
  8.     else
  9.     {
  10.         throw new Exception(String.Format("Unknown animal type {0}", type));
  11.     }
  12. }
  13.  
  14. public static AbstractAnimalBase GetAnimal(string type)
  15. {
  16.     AbstractAnimalBase cachedAnimal = GetCachedAnimal(type);
  17.     Type animalAssemblyType = cachedAnimal.GetType();
  18.  
  19.     AbstractAnimalBase animal = (AbstractAnimalBase)animalAssemblyType.Assembly.CreateInstance(animalAssemblyType.FullName);
  20.     return animal;
  21. }

Two things to notice here, there’s a method to get a cached animal as well as creating a new one based on the cached version.  I’ve supplied both just to show how to get a new animal from a cached version (this could also work for a normal cached factory as well as one using reflection to dynamically add classes).

Discovering what is available

And, that pretty much wraps up the factories and how to load classes dynamically.  What, you want to know what types are available to show to your UI?  Ok, here’s how you do that (requires “using System.Linq” namespace to be added thanks to the ToList extension method)…

AnimalFactory.cs
  1. public static List<string> GetAnimalTypes()
  2. {
  3.     return Animals.Keys.ToList<string>();
  4. }

Dynamic Cached Factory Code

Now, for all the dynamic cached factory code.

AbstractAnimalBase
  1. using System;
  2.  
  3. public abstract class AbstractAnimalBase
  4. {
  5.     public string AnimalType { get; private set; }
  6.  
  7.     public AbstractAnimalBase(string type)
  8.     {
  9.         AnimalType = type;
  10.     }
  11.  
  12.     public void IntroduceSelf()
  13.     {
  14.         System.Console.WriteLine(IntroductionLine);
  15.         System.Console.WriteLine(Speak);
  16.         System.Console.WriteLine();
  17.     }
  18.  
  19.     protected virtual string IntroductionLine
  20.     {
  21.         get
  22.         {
  23.             string introLine = "Hello, I am a " + AnimalType;
  24.             return introLine;
  25.         }
  26.     }
  27.  
  28.     protected abstract string Speak
  29.     {
  30.         get;
  31.     }
  32. }

 

Dog.cs
  1. using System;
  2.  
  3. public class Dog : AbstractAnimalBase
  4. {
  5.     public Dog()
  6.         : base("Dog")
  7.     {
  8.     }
  9.  
  10.     protected override string Speak
  11.     {
  12.         get { return "Woof"; }
  13.     }
  14. }

 

Cat.cs
  1. using System;
  2.  
  3. public class Cat : AbstractAnimalBase
  4. {
  5.     public Cat()
  6.         : base("Cat")
  7.     {
  8.     }
  9.  
  10.     protected override string Speak
  11.     {
  12.         get { return "Meow"; }
  13.     }
  14. }

 

Pig.cs
  1. using System;
  2.  
  3. public class Pig : AbstractAnimalBase
  4. {
  5.     public Pig()
  6.         : base("Pig")
  7.     {
  8.     }
  9.  
  10.     protected override string Speak
  11.     {
  12.         get { return "Oink"; }
  13.     }
  14. }

 

AnimalLoader.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4.  
  5. public class AnimalLoader
  6. {
  7.     public static List<AbstractAnimalBase> LoadAnimals(string filename)
  8.     {
  9.         List<AbstractAnimalBase> animals = new List<AbstractAnimalBase>();
  10.         Assembly assembly = Assembly.LoadFrom(filename);
  11.         Type[] types = assembly.GetTypes();
  12.  
  13.         foreach (Type type in types)
  14.         {
  15.             AbstractAnimalBase animal = null;
  16.             if (IsValidAnimal(type, ref animal))
  17.             {
  18.                 animals.Add(animal);
  19.             }
  20.         }
  21.         return animals;
  22.     }
  23.  
  24.     protected static bool IsValidAnimal(Type type, ref AbstractAnimalBase animal)
  25.     {
  26.         //is it a sub-class
  27.         if (type.BaseType != null)
  28.         {
  29.             //make sure it's not an interface
  30.             if (!type.IsInterface)
  31.             {
  32.                 //is it a sub-class of what we're interested in
  33.                 if (type.IsSubclassOf((typeof(AbstractAnimalBase))))
  34.                 {
  35.                     //make sure the type is a concrete type (i.e. not abstract)
  36.                     if (!type.IsAbstract)
  37.                     {
  38.                         try
  39.                         {
  40.                             animal = (AbstractAnimalBase)type.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { });
  41.                             return true;
  42.                         }
  43.                         catch (Exception ex)
  44.                         {
  45.                             throw new Exception(String.Format("Failed to load {0} as a add on command from {1}", type.Name, type.AssemblyQualifiedName), ex);
  46.                         }
  47.                     }
  48.                 }
  49.             }
  50.         }
  51.         return false;
  52.     }
  53. }

 

AnimalFactory.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Linq;
  5.  
  6. public static class AnimalFactory
  7. {
  8.     private static Dictionary<string, AbstractAnimalBase> Animals { get; set; }
  9.  
  10.     static AnimalFactory()
  11.     {
  12.         Animals = new Dictionary<string, AbstractAnimalBase>();
  13.  
  14.         List<AbstractAnimalBase> animals;
  15.  
  16.         string filename = "Animals.dll";
  17.         string path = Assembly.GetExecutingAssembly().Location;
  18.         path = System.IO.Path.GetDirectoryName(path);
  19.         string fileToLoad = System.IO.Path.Combine(path, filename);
  20.  
  21.         try
  22.         {
  23.             animals = AnimalLoader.LoadAnimals(fileToLoad);
  24.  
  25.             foreach (AbstractAnimalBase animal in animals)
  26.             {
  27.                 string animalType = animal.AnimalType.ToLower().Trim();
  28.                 if (!Animals.ContainsKey(animalType))
  29.                 {
  30.                     Animals.Add(animalType, animal);
  31.                 }
  32.                 else
  33.                 {
  34.                     throw new Exception(String.Format("Duplicate animal type {0} found", animalType));
  35.                 }
  36.             }
  37.         }
  38.         catch (Exception ex)
  39.         {
  40.             throw new Exception(String.Format("There was a problem loading the assembly {0}", fileToLoad), ex);
  41.         }
  42.     }
  43.  
  44.     public static AbstractAnimalBase GetCachedAnimal(string type)
  45.     {
  46.         type = type.ToLower().Trim();
  47.         if (Animals.ContainsKey(type))
  48.         {
  49.             return Animals[type];
  50.         }
  51.         else
  52.         {
  53.             throw new Exception(String.Format("Unknown animal type {0}", type));
  54.         }
  55.     }
  56.  
  57.     public static AbstractAnimalBase GetAnimal(string type)
  58.     {
  59.         AbstractAnimalBase cachedAnimal = GetCachedAnimal(type);
  60.         Type animalAssemblyType = cachedAnimal.GetType();
  61.  
  62.         AbstractAnimalBase animal = (AbstractAnimalBase)animalAssemblyType.Assembly.CreateInstance(animalAssemblyType.FullName);
  63.         return animal;
  64.     }
  65.  
  66.     public static List<string> GetAnimalTypes()
  67.     {
  68.         return Animals.Keys.ToList<string>();
  69.     }
  70. }

No comments:

Post a Comment