JAL Computing

C++COMProgramming .NET Mac Palm CPP/CLI Hobbies

 

Home
Up

Chapter 18 "Dynamic Class Factories"

Updated 11.25.2006

In Chapter 4 you learned how to use a class factory to create concrete objects at runtime that implement a common interface or base class. The sample code in Chapter 4 used a "switch" on an enum to select the target class. 

/// <summary>
///
Summary description for class Factory.
/// </summary>
class
Factory
{

    public
enum DrawableType {CIRCLE,SQUARE};
    public
static Drawable GetInstance(DrawableEnum e)
    {
        if (e == DrawableType.CIRCLE)
        {
            return new Circle();
        }

        else
if (e == DrawableType.SQUARE)
        {
            return new Square();
        }

        else
        {
            throw new IndexOutOfRangeException(); // should never get here
        }
    }


The use of a "switch" on an enum is not very object oriented and not very extensible at compile time. For instance, to add a new concrete Triangle class we need to modify and recompile the Factory class and DrawableType enum. More importantly, the use of a switch does not allow runtime addition of new classes (plugins) to the class factory.

Note: It is possible to write a class factory that can be extended at compile time, allowing the addition of additional known classes at compile time.

        public virtual IDrawable GetInstance(string key)
        {
            if (key == "Circle") 
            {
                return new Circle();
            }
            else if (key == "Square")
            {
                return new Square();
            }
            else
            {
                return null;
            }
        }

This class can be extended to add another class at compile time, reusing the code in the base class as in:

        public override IDrawable GetInstance(string key)
        {
            if (key == "Oval") 
            {
                return new Oval();
            }
            else
            {
                return base.GetInstance(key);
            }
        }

In this chapter you will learn two approaches to creating a "growable" class factory that allows the addition of new classes at run time. The first approach simply makes use of Types and Activator.CreateInstance to build a class factory. The second approach abstracts object creation into an Interface, IConstructDrawableShape, which provides a method Instantiate(). The Instantiate() method returns a reference of type IDrawableShape. The interface based class factory makes use of a helper class to adapt an existing Drawable class to a menu driven class factory. 

Efficiency

The use of a type based class factory (late binding) is not as efficient as a static class factory (early binding) as discussed at MSDN: Efficient Reflection. If efficient object creation is essential it is possible to combine both early and late bound object creation into one method. Classes that are known at compile time are handled in a base class factory that uses early binding. Classes that are discovered at runtime are handled in a derived class factory using late binding. Request are first handled by the late bound classed factory. Any un-handled request can then be passed to the early bound class factory (See Chapter 35).

Create the Composite Interface

The first step is to declare the IDrawableShape interface and code an abstract base class, AbstractDrawableShape, that provides default implementations of the properties and methods in IDrawableShape. Here is the refactored composite IDrawableShape interface from Chapter 16 "Designing Interfaces.":

	// Refactored Drawable Interface
	public interface IDrawable
	{
		void DrawYourself(Graphics g);
	}
	// ASSERT P.X>=0 and P.Y>=0;
	public interface IPositional
	{
		Point Position
		{
			get;
			set;
		}
	}
	public interface ISizeable
	{
		int Size
		{
			get;
			set;
		}
	}
	public interface IColorable 
	{
		Color Color 
		{
			get;
			set;
		}
	}

	// Extensible Composite Interface IDrawableShape
	public interface IDrawableShape : IDrawable, IPositional, IColorable, ISizeable {}
	

Note: Since the Position property explicitly ASSERTS that Position.x and Position.y must be >= 0 (pre conditions), any concrete class that implements this property should validate Position input and throw an ArgumentException if the Position input violates the explicitly stated precondition.

Provide a Default Implementation of IDrawableShape

To simplify coding, you should provide an abstract base class called AbstractDrawableShape with default implementations of the getter and setter properties. Any concrete class of IDrawableShape can simply inherit the default implementation by extending the AbstractDrawableShape class and overriding the DrawYourself method. Here is the abstract base class:

	// Factor repeated code into Abstract Class with Default Implementations
	// Concrete classes of IDrawableShape should inherit from this class
	// and override DrawYourself
	public abstract class AbstractDrawableShape : IDrawableShape 
	{
		private int size=1;
		private Point position= new Point(0,0);
		private Color color= Color.Black;
		public int Size
		{
			get {return size;}
			set 
			{
				if (value>0) {size= value;}
			}
		}
		// ASSERT p.x and p.y >=0
		public Point Position
		{
			get {return position;}
			set 
			{
				if ((value.X >=0) && (value.Y >=0))
				{
					position= value;
				}
				else 
				{
					throw new ArgumentException();
				}
			}
		}
		public Color Color
		{
			get{return color;}
			set 
			{
				color= value;
			}
		}
		virtual public void DrawYourself(Graphics g)
		{
			g.DrawString("Drawable",
				new Font("Arial",size),
				new SolidBrush(color),
				position.X, 
				position.Y,
				StringFormat.GenericDefault);
		}
	}

 

Type Based Class Factory

The next step is to create a simple type based class factory that uses Activator.CreateInstance to create a growable class factory. The TypeBasedDrawableFactory contains a private Hashtable of Types t. The factory returns a concrete object by calling Activator.CreateInstance(t). Classes in a "plugins" folder can be added to the factory by obtaining their type at runtime using reflection. Here is the code for the type based class factory:

// the class factory
class TypeBasedDrawableFactory 
{
	private Hashtable hash= new Hashtable();
	public bool Add(Type t) 
	{
		if ((t != null)&& 
			!hash.Contains(t.Name) 
			&& (typeof(IDrawableShape).IsAssignableFrom(t))
			&& !t.IsAbstract)
		{
			hash.Add(t.Name,t);
			return true;
		}
		else return false;
	}
	public int Count() 
	{
		return hash.Count;
	}
	public ICollection Keys() 
	{
		return hash.Keys;
	}
	public IDrawableShape GetInstance(string key) 
	{
		if (key == null || !hash.Contains(key)) 
		{
			return null;
		}
		else 
		{
			Type t= (Type)hash[key];
				// dynamically load this class
				return (IDrawableShape)Activator.CreateInstance(t);
			//else return null;
		}
	}
	public bool Contains(string key) 
	{
		if (key != null) 
		{
			return hash.Contains(key);
		}
		else {return false;}
	}
}

Items can be added statically to the type based class factory at compile time using the syntax:

dynamicFactory.Add(typeof(Circle));
dynamicFactory.Add(typeof(Square));

Items can also be added at runtime using reflection. Here is a snippet of code that dynamically adds a DrawableShape type in a "plugins" folder at runtime:

	string[] dir= Directory.GetFiles(path,"*.dll");
	// iterate over files in folder plugins
	foreach (string s in dir)
	{
		string dll= Path.GetFileNameWithoutExtension(s);
		assembly= Assembly.Load(dll);  // in folder plugins
		//Type[] types= assembly.GetTypes();
		Type[] types= assembly.GetExportedTypes();  //safer
		foreach(Type t in types) 
		{
			//Console.WriteLine("Type: {0}",t);
			try 			
			{
				// only load into type based factory
				// if type implements IDrawableShape
				if (typeof(IDrawableShape).IsAssignableFrom(t)
					&& !t.IsAbstract)) // safer
				{
					typeFactory.Add(t);
				}
				// only load into abstract factory if
				// type implements IConstructDrawableShape
				if (typeof(IConstructDrawableShape).IsAssignableFrom(t))
				{
		abstractFactory.Add((IConstructDrawableShape)Activator.CreateInstance(t));
				}
			}
			catch(Exception e) 
			{
				Console.WriteLine(e);
			}
		}
	}

 As implemented, if the factory already contains an item with the same key, the item is not added to the factory.

The type based factory TypeBasedDrawableFactory contains a Hashtable of IDrawableShape Types. To construct an object that implements IDrawableShape, you simply pass a valid name (key) to obtain the appropriate IDrawableShape type in the private Hashtable. 

Interface Based Class Factory

An alternative to a type based class factory is an interface based class factory that abstracts the concept of object creation into an Interface, IConstructDrawableShape. This solution requires the use of a helper class for each DrawableShape class that will be added to the class factory. For each statically compiled class that implements IDrawableShape, you create an instance of a class that implements IConstructDrawableShape. The IConstructDrawableShape class provides an implementation of the Instantiate() method that instantiates an object and then returns a reference to the object of type IDrawableShape. The interfaced based class factory is more efficient than the typed based class factory due to the increased overhead associated with type based reflection.

The helper class that implements IConstructDrawableShape also adds a level of indirection to the creation of a IDrawableShape object. The adapter Name property can differ from the actual Drawable class name. In other words, the helper class maps a menu name for a specific concrete DrawableShape object. This name can be used to populate a menu. When the user selects a named item from the menu, the appropriate object is returned by the ConstructDrawableShape class.  The added level of indirection provides an opportunity to set an instance property or call a specific class constructor at runtime. To demonstrate the advantage of this extra level of indirection, this project statically adds a "Green Circle" and "Blue Square" to the class factory. The project then dynamically adds a "Blue Triangle" at runtime from a "plugins" folder. When the user selects a "Green Circle" from a dynamically populated menu, a Circle is returned with the Color property set to Color.Green.

The Interface DLL

You can first abstract object creation by defining a new Interface, IConstructDrawableShape, in a separate DLL project. Here is the new interface: 

	// Abstract Object Creation into an Interface IConstructDrawableShape
	public interface IConstructDrawableShape
	{
		string Name {get;}
		IDrawableShape Instantiate();
	}

The following code should be compiled into a separate DLL in release mode. Users of these interfaces or abstract classes need to add a reference to this DLL to their project.

using System;
using System.Drawing;
namespace JALInterfaces
{
	// Refactored Drawable Interface
	public interface IDrawable
	{
		void DrawYourself(Graphics g);
	}
	// ASSERT P.X>=0 and P.Y>=0;
	public interface IPositional
	{
		Point Position
		{
			get;
			set;
		}
	}
	public interface ISizeable
	{
		int Size
		{
			get;
			set;
		}
	}
	public interface IColorable 
	{
		Color Color 
		{
			get;
			set;
		}
	}
	// Extensable Composite Interface IDrawableShape
	public interface IDrawableShape : IDrawable, IPositional, IColorable, ISizeable {}
	
	// Abstract Object Creation into an Interface IConstructDrawableShape
	public interface IConstructDrawableShape
	{
		string Name {get;}
		IDrawableShape Instantiate();
	}
	// Factor repeated code into Abstract Class with Default Implementations
	// Concrete classes of IDrawableShape should inherit from this class
	// and override DrawYourself
	public abstract class AbstractDrawableShape : IDrawableShape 
	{
		private int size=1;
		private Point position= new Point(0,0);
		private Color color= Color.Black;
		public int Size
		{
			get {return size;}
			set 
			{
				if (value>0) {size= value;}
			}
		}
		// ASSERT p.x and p.y >=0
		public Point Position
		{
			get {return position;}
			set 
			{
				if ((value.X >=0) && (value.Y >=0))
				{
					position= value;
				}
				else 
				{
					throw new ArgumentException();
				}
			}
		}
		public Color Color
		{
			get{return color;}
			set 
			{
				color= value;
			}
		}
		virtual public void DrawYourself(Graphics g)
		{
			g.DrawString("Drawable",
			new Font("Arial",size),
			new SolidBrush(color),
			position.X, 
			position.Y,
			StringFormat.GenericDefault);
		}
	}
}

The DrawablePlugIns DLL

Here is a new version of a Triangle class that should be compiled into yet another release DLL and the DLL should then be placed into a "plugins" folder at the level of the Windows Form exe. Again, you may need to add a reference to JALInterfaces.dll. This separate project is used to create a new concrete implementation of IDrawableShape, a Triangle, that can be added dynamically at runtime. To demonstrate the utility of abstracting object creation, the project adds a "Blue Triangle" class constructor.

using System;
namespace DrawablePlugIns
{
	// Separate DLL used to create a new concrete class Triangle
	// This DLL can then be added to a "plugins" folder
	// An application can then dynamically add the Triangle class
	// at runtime!
	using System;
	using System.Drawing; // project references System.Drawing.dll
	using JALInterfaces; // project references JALInterfaces.dll
	namespace DrawPlugIn
	{
		// Create a new concrete class Triangle to be loaded dynamically
		public class Triangle : AbstractDrawableShape
		{
			public override void DrawYourself(Graphics g)
			{
					g.DrawString("Triangle",
			new Font("Arial",Size),
			new SolidBrush(Color),
			Position.X,
			Position.Y,
			StringFormat.GenericDefault);
			}
		}
		// Create a Triangle Constructor for use with an abstract factory
		// The extra degree of indirection allows us to customize the
		// state of the Triangle
		public class BlueTriangleConstructor : IConstructDrawableShape 
		{
			public string Name 
			{
				get { return "Blue Triangle";}
			}
			public IDrawableShape Instantiate() { 
				Triangle temp= new Triangle();
				temp.Color= Color.Blue;
				return temp;
			}
		}
	}
}

The Interface Based Class Factory

Just like the type based class factory, the interface based class factory wraps a hashtable of objects and provides key based access. Here is the code:

	class AbstractDrawableFactory 
	{
		private Hashtable hash= new Hashtable();
		public bool Add(IConstructDrawableShape item) 
		{
			if ((item != null)
				&& (!hash.Contains(item.Name))
				&& !item.IsAbstract)) 
			{
				hash.Add(item.Name,item);
				return true;
			}
			else {return false;}
		}
		public int Count() 
		{
			return hash.Count;
		}
		public ICollection Keys() 
		{
			return hash.Keys;
		}
		public IDrawableShape GetInstance(string key) 
		{
			if (key == null || !hash.Contains(key)) 
			{
				return null;
			}
			else 
			{
		return ((IConstructDrawableShape)hash[key]).Instantiate();
			}
		}
		public bool Contains(string key) 
		{
			if (key != null) 
			{
				return hash.Contains(key);
			}
			else {return false;}
		}
	}
 

The abstract factory AbstractDrawableFactory contains a Hashtable of concrete instances of IConstructDrawableShape objects. To construct an object that implements IDrawableShape, you simply pass a valid name (key) to obtain the appropriate IConstructDrawableShape object in the private Hashtable. 

The Window Forms Program

Here is a Windows Form project that contains both a type based factory and an interface based class factory that abstracts object creation.

Notice that all of the abstract (interface based) factory objects were colored to distinguish them from the black, type based factory objects. Here is the Circle code that is compiled statically:

class Circle : AbstractDrawableShape
{
	public override void DrawYourself(Graphics g)
	{
		g.DrawString("Circle",
			new Font("Arial",Size),
			new SolidBrush(Color),
			Position.X,
			Position.Y,
			StringFormat.GenericDefault);
	}
}

Here is a statically compiled "Green Circle" constructor:


class GreenCircleConstructor: IConstructDrawableShape 
{
	public string Name 
	{
		get {return "Green Circle";}
	}
	public IDrawableShape Instantiate() 
	{
		IDrawableShape temp= new Circle();
		temp.Color= Color.Green;
		return temp;
	}
}

The Whole Tamale

Here is the complete Windows Form application code that dynamically adds a Triangle class from a plugins folder at runtime.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using JALInterfaces;
using System.Reflection;
using System.IO;
namespace TestDrawFactory
{	
	/// <summary>
	/// Summary description for Form1.
	/// Demonstrates the use of growable class factories
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;
		// create our empty factories
private AbstractDrawableFactory abstractFactory= new AbstractDrawableFactory();
private TypeBasedDrawableFactory typeFactory= new TypeBasedDrawableFactory();
		public Form1()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();
			//
			// TODO: Add any constructor code after InitializeComponent call
			//
			// add Statically Compiled classes to our factories
			// abstract factory
			abstractFactory.Add(new GreenCircleConstructor());
			abstractFactory.Add(new RedSquareConstructor());
			// type based factory
			typeFactory.Add(typeof(Circle));
			typeFactory.Add(typeof(Square));
			// now dynamically load classes in a plugin folder which
			// implement JALInterfaces.IDrawableShape and IConstructDrawableShape
			Assembly assembly= null;
			try 
			{        
				// tell app where to look for plugins
				AppDomain.CurrentDomain.AppendPrivatePath("plugins");
				// get absolute path to our private assemblies
				string path= AppDomain.CurrentDomain.BaseDirectory+"plugins";
				
				// create plugins folder if one does not exist
				DirectoryInfo info= new DirectoryInfo(path);
				if (!info.Exists){ info.Create();}
				// discover all dlls in plugins folder
				string[] dir= Directory.GetFiles(path,"*.dll");
				// iterate over files in folder plugins
				foreach (string s in dir)
				{
					string dll= Path.GetFileNameWithoutExtension(s);
					assembly= Assembly.Load(dll);  // in folder plugins
					Type[] types= assembly.GetTypes();
					Type[] types= assembly.GetExportedTypes();  //safer
					foreach(Type t in types) 
					{
						//Console.WriteLine("Type: {0}",t);
						try 			
						{
							// only load into type based factory
							// if type implements IDrawableShape
				if (typeof(IDrawableShape).IsAssignableFrom(t)
						&& !t.IsAbstract) {
								typeFactory.Add(t);
				}
		// only load into abstract factory if
		// type implements IConstructDrawableShape
		if (typeof(IConstructDrawableShape).IsAssignableFrom(t))
		{
		abstractFactory.Add((IConstructDrawableShape)Activator.CreateInstance(t));
		}
						}
						catch(Exception e) 
						{
							Console.WriteLine(e);
						}
					}
				}
			// Both class factories are now ready
			}
			catch(Exception e) 
			{
				Console.WriteLine(e);
			}
		}
		protected override void OnPaint(PaintEventArgs e) 
		{
			Graphics g= e.Graphics;
			int x=0;
			int y=0;
			int position= 20;
			int size= 20;
			// Test our class factories
			// Iterate over constructors in the abstract class factory
			// Invoke DrawYourself on each item
			// In this example these all create colored shapes
			ICollection collection= abstractFactory.Keys();
			IEnumerator enumerator= collection.GetEnumerator();
			while (enumerator.MoveNext())
			{
IDrawableShape shape= (IDrawableShape)abstractFactory.GetInstance((string)enumerator.Current);
				shape.Position= new Point(x,y);
				shape.Size= size;
				shape.DrawYourself(g);
				y+= position;
				// display menu name
				System.Console.WriteLine(enumerator.Current);
			}
			// Now iterate over objects in our type based factory
			// We will make these all black
			collection= typeFactory.Keys();
			enumerator= collection.GetEnumerator();
			while (enumerator.MoveNext())
			{
IDrawableShape shape= (IDrawableShape)typeFactory.GetInstance((string)enumerator.Current);
				shape.Position= new Point(x,y);
				shape.Size= size;
				shape.Color= Color.Black;
				shape.DrawYourself(g);
				y+= position;
				// display menu name
				System.Console.WriteLine(enumerator.Current);
			}
		}
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}
		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.components = new System.ComponentModel.Container();
			this.Size = new System.Drawing.Size(300,300);
			this.Text = "Form1";
		}
		#endregion
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}
	}
	class Square : AbstractDrawableShape
	{
		public override void DrawYourself(Graphics g)
		{
			g.DrawString("Square",
				new Font("Arial",Size),
				new SolidBrush(Color),
				Position.X,
				Position.Y,
				StringFormat.GenericDefault);
		}
	}
	class RedSquareConstructor : IConstructDrawableShape
	{
		public string Name
		{
			get {return "Red Square";}
		}
		public IDrawableShape Instantiate() 
		{
			IDrawableShape temp= new Square();
			temp.Color= Color.Red;
			return temp;
		}
	}
	class Circle : AbstractDrawableShape
	{
		public override void DrawYourself(Graphics g)
		{
			g.DrawString("Circle",
				new Font("Arial",Size),
				new SolidBrush(Color),
				Position.X,
				Position.Y,
				StringFormat.GenericDefault);
		}
	}
	class GreenCircleConstructor: IConstructDrawableShape 
	{
		public string Name 
		{
			get {return "Green Circle";}
		}
		public IDrawableShape Instantiate() 
		{
			IDrawableShape temp= new Circle();
			temp.Color= Color.Green;
			return temp;
		}
	}
	
// the class factory
class TypeBasedDrawableFactory 
{
	private Hashtable hash= new Hashtable();
	public bool Add(Type t) 
	{
		if ((t != null)&& 
			!hash.Contains(t.Name) 
			&& (typeof(IDrawableShape).IsAssignableFrom(t))
			&& !t.IsAbstract)
		{
			hash.Add(t.Name,t);
			return true;
		}
		else return false;
	}
	public int Count() 
	{
		return hash.Count;
	}
	public ICollection Keys() 
	{
		return hash.Keys;
	}
	public IDrawableShape GetInstance(string key) 
	{
		if (key == null || !hash.Contains(key)) 
		{
			return null;
		}
		else 
		{
			Type t= (Type)hash[key];
				// dynamically load this class
				return (IDrawableShape)Activator.CreateInstance(t);
			//else return null;
		}
	}
	public bool Contains(string key) 
	{
		if (key != null) 
		{
			return hash.Contains(key);
		}
		else {return false;}
	}
}

	class AbstractDrawableFactory 
	{
		private Hashtable hash= new Hashtable();
		public bool Add(IConstructDrawableShape item) 
		{
			if ((item != null)
				&& (!hash.Contains(item.Name))
				&& !item.IsAbstract) 
			{
				hash.Add(item.Name,item);
				return true;
			}
			else {return false;}
		}
		public int Count() 
		{
			return hash.Count;
		}
		public ICollection Keys() 
		{
			return hash.Keys;
		}
		public IDrawableShape GetInstance(string key) 
		{
			if (key == null || !hash.Contains(key)) 
			{
				return null;
			}
			else 
			{
				return ((IConstructDrawableShape)hash[key]).Instantiate();
			}
		}
		public bool Contains(string key) 
		{
			if (key != null) 
			{
				return hash.Contains(key);
			}
			else {return false;}
		}
	}
}

End of the Whole Tamale

In this chapter you learned two object oriented approaches to creating a "growable" class factory that can be expanded at runtime. Class factories allow the dynamic instantiation of classes selected from a menu at runtime. Static class factories use efficient early binding. Type based class factories use late binding, but support the dynamic addition of new "plug in" classes at run time. It is possible to combine the two factories using inheritance as seen in Chapter 35. In the next Chapter, you will learn how to dynamically create the appropriate Menu sub items at runtime and actually draw the DrawableShapes on mouse up.

Have fun!

Send mail to [email protected] with questions or comments about this web site. Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 © 
Last modified: 08/04/09
Hosted by www.Geocities.ws

1