To start with, let's create an interface for Commands. For this example, I am using a base namespace of BlogSandox.
namespace BlogSandbox.command
{
public interface ICommand
{
string CommandName { get; }
string Execute(string input);
}
}
All Commands will implement this interface and provide a command name (this is used to map the command to its instance). To simplify all implementations, we will provide an abstract base class.namespace BlogSandbox.command
{
public abstract class BaseCommand : ICommand
{
private readonly string _commandName;
protected BaseCommand(string commandName)
{
this._commandName = commandName;
}
public string CommandName
{
get { return _commandName; }
}
public abstract string Execute(string input);
}
}
Now let's create a couple example Commands. Admittedly, these are pretty useless, but should help get the point across.namespace BlogSandbox.command
{
class EchoCommand : BaseCommand
{
public const string NAME = "EchoCommand";
public EchoCommand()
: base(NAME)
{
}
public override string Execute(string input)
{
return input;
}
}
}
namespace BlogSandbox.command
{
class GreetCommand : BaseCommand
{
public const string NAME = "GreetCommand";
public GreetCommand()
: base(NAME)
{
}
public override string Execute(string input)
{
return "Hello " + input + "!";
}
}
}
Now for the command factory. This factory is going to register each instance using the "CommandName" provided by the implementing classes. While this example doesn't have a "collision" detection (e.g. when 2 commands have the same name), it is not a bad idea to add one if you will be dealing with a lot of commands and especially if you will be making them pluggable.
namespace BlogSandbox.command
{
public class CommandFactory
{
private readonly Dictionary _commandImplementations;
private static volatile CommandFactory _commandFactoryInstance;
private static object _syncRoot = new object();
private CommandFactory()
{
_commandImplementations = new Dictionary();
Initialize();
}
public static CommandFactory Instance
{
get
{
if (_commandFactoryInstance == null)
{
lock (_syncRoot)
{
_commandFactoryInstance = new CommandFactory();
}
}
return _commandFactoryInstance;
}
}
public ICommand GetCommand(string commandName)
{
return _commandImplementations[commandName.ToLower()];
}
private void Initialize()
{
// 1. get assembly
Assembly asm = Assembly.GetCallingAssembly();
// 2. get the list of all types in this assembly and iterate
foreach (Type type in asm.GetTypes())
{
// we want the non-abstract implementations of ICommand
if (type.IsClass && !type.IsAbstract)
{
Type iCommand =
type.GetInterface(
"BlogSandbox.command.ICommand");
if (iCommand != null)
{
// create an instance
object inst =
asm.CreateInstance(type.FullName, true,
BindingFlags.CreateInstance, null, null,
null, null);
if (inst != null)
{
ICommand commandInst = (ICommand)inst;
// make it case insensitive
string key =
commandInst.CommandName.ToLower();
_commandImplementations.Add(
key, commandInst);
}
else
{
string errMsg =
"CommandFactory.Initialize(): Unable " +
"to properly initialize " +
"CommandFactory - there was a " +
"problem instantiating the class " +
type.FullName;
throw new Exception(errMsg);
}
}
}
}
}
}
}
The main thing to catch here is the use of the "Assembly" to find the classes and then load the ones that are of the appropriate type (e.g. non-abstract class that implements our command interface ICommand). To see this working, here is a simple main that you can add for a Console Application. The sleep at the bottom keeps the window open long enough to see the results... of course, you could use a logger or other application type that doesn't close up at completion.namespace BlogSandbox
{
class Program
{
static void Main(string[] args)
{
CommandFactory factory = CommandFactory.Instance;
Console.WriteLine("Echo Command: " +
factory.GetCommand(EchoCommand.NAME).
Execute("Repeat me"));
Console.WriteLine("Greet Command: " +
factory.GetCommand(GreetCommand.NAME).
Execute("Paul"));
Thread.Sleep(20000);
}
}
}
These commands are clearly very simplified, but I hope that you can see how this can be applied in your own use case. In a full implementation that I wrote for work, the BaseCommand method had protected methods for performing validations on the commands, returned a CommandResponse object where errors could be added in a list or a successful response could be provided (such as returning a result of the action). Additionally, for building my commands I used a fantastic command line parser library. I highly recommend this library as it really simplified taking in command arguments. This simplified example really only touches on the factory for commands, but doesn't really discuss a good way to implement the commands, nor how to parse a command line. In a future post, I will explore the Command Line Parser Library I used and try to build from this example so that you can see more of the whole picture. I hope this helps someone out there! As always, feel free to use my code - while not necessary, attribution in the form of a link back to this post is always appreciated!
No comments:
Post a Comment