I like to show a variation of the dictionary approach others already proposed. Building on this that solution, you could do the following.
1 Define a base class:
public abstract class JobDoer
{
public abstract void DoJob();
}
2 Define a attribute for decoration of job doers.
public sealed class JobDoerAttribute : Attribute
{
JobDoerAttribute(string jobDoerId)
{
this.JobDoerId = new Guid(jobDoerId);
}
public Guid JobDoerId { get; private set; }
}
3 Define the actual job doer classes that are decorated with that attribute. For instance:
[JobDoer("063EE2B2-3759-11DF-B738-49BB56D89593")]
public sealed class SpecificJobDoer : JobDoer
{
public override void DoJob()
{
// Do a specific job
}
}
4 Define a JobDoerFactory
that enables retrieving JobDoer
instances by their Id as it is defined in the attribute:
public static class JobDoerFactory
{
static Dictionary<Guid, JobDoer> cache;
static JobDoerFactory()
{
// Building the cache is slow, but it will only run once
// during the lifetime of the AppDomain.
cache = BuildCache();
}
public static JobDoer GetInstanceById(Guid jobDoerId)
{
// Retrieving a JobDoer is as fast as using a switch statement.
return cache[jobDoerId];
}
private static Dictionary<Guid, JobDoer> BuildCache()
{
// See implementation below.
}
}
In the BuildCache
method, you can do the loading of JobDoer
instances by using reflection.
private static Dictionary<Guid, JobDoer> BuildCache()
{
// This is a bit naive implementation; we miss some error checking,
// but you'll get the idea :-)
var jobDoers =
(from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where type.IsSubclassOf(typeof(JobDoer))
let attributes =
type.GetCustomAttribute(typeof(JobDoerAttribute), true)
where attributes.Length > 0
let attribute = attributes[0] as JobDoerAttribute
select new { attribute.JobDoerId, type }).ToArray();
var cache = new Dictionary<Guid, JobDoer>(jobDoers.Length);
foreach (jobDoer in jobDoers)
{
// Note that actually a single instance of the job doer is
// cached by ID. This means that every Job Doer must be
// thread-safe and usable multiple times. If this is not
// feasable, you can also create store a set of Func<JobDoer>
// objects that enable creating a new instance on each call.
cache[jobDoer.JobDoerId] =
(JobDoer)Activator.CreateInstance(jobDoer.type);
}
return cache;
}
I didn't test this code, so I don't know if it compiles, but I used this mechanism in a project a few years back. This way it is easy to define new classes, without the need to hook it up to some dictionary. It is done automatically at runtime.
This might look a bit like overkill, but if you have +2000 JobDoer
classes, this could help you a lot.
Update:
Note that if you don't like the idea of the JobDoerAttribute
, you can also implement it as abstract property on the abstract JobDoer
class. However, I've found using an attribute makes the code very explicit and expressive.