My poor man’s factory pattern in c# – one way of instantiating all derived classes of an abstract base class

Apologies for deleting yesterday’s post. It used my actual code and the syntax highlighting was broken in my Open Live Writer. This one uses simplified code that I wrote in an example solution, and thus does not involve me sharing code that I do not own, and I managed to get the Open Live Writer plugin working again.

In a recent post I mentioned my “poor man’s factory pattern”. I’m sharing it here in case anyone needs something similar.

As I understand it, a factory pattern, typically implemented as either an abstract factory or a factory method, is a pattern where your classes decide which class to instantiate. So it’s a pattern all about instantiating objects, typically related objects that are all registered products used by association by some sort of owner, or factory.

Well, I wanted something similar, but also wanted to keep it simple.

I have a control that exposes a dialog, which is used by a user to capture data via a USB device. I have three different such devices, each with a completely different API, and so my project uses all of them via their SDK’s. You don’t need to know the details. The point is, a user might have any one of those three devices plugged in. My capture dialog needs to use whichever one is plugged in (or the first one that works if the user plugs more than one in, but they are not expected to do that).

So what I did was:

  1. Create a base class, an abstract class called Device.
  2. One of its properties is called DeviceFound, which returns a bool.
  3. Each derived type only sets that true if the device is plugged in and working. (Again, how they do that is implementation-specific details you don’t need to know.)

The control that uses the device contains a single property of the base type. So all that needs to happen, in the constructor of the control, is this:

  1. Iterate all the types in my assembly that are subtypes of Device, are not abstract, and have a parameterless public constructor.
  2. Create each one and check if DeviceFound is true.
  3. If that property is true, use that device; otherwise try the next one.
  4. For clarity, “use that device” means assign the working subtype found to the control’s single Device property, and use it when the user scans an image. If no working device is found, although that’s outside of the scope of this post, I simply display an error. (That code is not shown here.)

That is, I have three subtypes, but only want to use one of them; the first one I find to be valid. Unfortunately I must instantiate each in turn to check if it is the right one. It sounds expensive but in practice is really very fast.

Here is the code:

/* Get all subtypes of Device that are not abstract. This won't work with interfaces
 * but that's OK because we defined the base class as the Device abstract class. */
var scanners = from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
               from assemblyType in domainAssembly.GetTypes()
               where assemblyType.IsClass && !assemblyType.IsAbstract && assemblyType.IsSubclassOf(typeof(Device))
               select assemblyType;

foreach (Type type in scanners)
        var constructor = (type).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
        if (constructor != null)
            var scanner = constructor.Invoke(null) as Device;
            if (scanner.DeviceFound)
                this.scanner = scanner;
        errorMessages.Add(string.Format("Unable to instantiate {0} scanner.", type.ToString()));

The technique used here to find all subtypes of a type is based on the most popular answer on this StackOverflow question, except I optimized it somewhat to short circuit out anything found that isn’t a concrete class, and it uses Type.IsSubclassOf rather than Type.IsAssignableFrom, because I did not want the base class in my list, and it does not need to find interfaces.

If you want to do something similar, you can use IsAssignableFrom for interfaces instead. It could be further optimized to omit the check for a null constructor but I like it the way it is. I also need my control to behave and work even if, for example, two of the three device drivers have not been installed, in which case the code must still use the device that works (and swallow the exceptions for the other devices). Also, I do not take their last step and copy the enumeration to an array, for brevity. This is only an example. In production code, especially if your list of derived types is not going to change, you might want to cache the results in a List<Type> or array, and not enumerate every time your code is called.

And for interest sake, this is what my abstract class looks like:

using System.Collections.Generic;
using System.Linq;

namespace Example
    public abstract class Device
        #region Properties

        public abstract bool DeviceFound { get; }

        public virtual IEnumerable<string> ErrorMessages
                return Enumerable.Empty<string>();

        public abstract string Wsq64BitImage { get; }

        #endregion Properties

        #region Methods

        #region Public Methods

        public abstract bool ScanImage();

        #endregion Public Methods

        #endregion Methods

About Jerome

I am a senior C# developer in Johannesburg, South Africa. I am also a recovering addict, who spent nearly eight years using methamphetamine. I write on my recovery blog about my lessons learned and sometimes give advice to others who have made similar mistakes, often from my viewpoint as an atheist, and I also write some C# programming articles on my programming blog.
This entry was posted in Programming and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s