Yep. I suppose your idea is generaly fine. This is being called Command Pattern. You might like to read more about it on Google. Used together with other well known pattern Strategy they both might be used here. Plus another one - Template Method. Let me just suggest some basic approach:
public abstract class BaseCommand
{
protected BaseCommand()
{
}
public void Process(StuffOptions options)
{
Logging.Log("here is a bunch of logging");
// here sometimes there is some action-specific code but not often
ExtraStuff();
using (DocWrapper doc = new DocWrapper(options.File)) // this is in all actions
{
foreach (int page in doc.GetPagesToModify(options.Pages)) // this is in most actions
{
// call some stuff on the doc instance
DoRealStuff(doc);
}
doc.Save(options.OutputFile); // this is in all actions
}
}
protected abstract void DoRealStuff(DocWrapper doc);
protected virtual void ExtraStuff()
{
}
}
So, now you can go and implement your commands. You have to ALWAYS implement abstract method DoRealStuff()
but only override ExtraStuff()
when needed.
class BaseCommandA : BaseCommand
{
protected override void DoRealStuff(DocWrapper doc)
{
// Doing my stuff here!
}
}
class BaseCommandB : BaseCommand
{
protected override void ExtraStuff()
{
base.ExtraStuff();
// I have to set some extra paramaetrs here...
}
protected override void DoRealStuff(DocWrapper doc)
{
// Doing my other stuff here!
}
}
Then there is matter of implementing last part - strategy. You can keep your static switch-get approach if you like, bu t better is to hide your classes details and return commands only by their base type:
public static class CommandFactory
{
private static BaseCommandA commandAInstance = new BaseCommandA();
private static BaseCommandB commandBInstance = new BaseCommandB();
public static BaseCommand GetCommand(invokedVerb)
{
switch (invokedVerb)
{
case "barcode": return commandAInstance;
case "addblankpage": return commandBInstance;
// ...
}
}
}
You might like to go into some lazy initialization inside if you like...
And then - grande finale - using all this stuff:
public void Processing(cmd)
{
CommandFactory.GetCommand(cmd.Verb).Process(cmd.Options);
// ...
}
Ofcourse all types, and arguments are being simplified. Good luck and have nice coding.
EDIT: And yes - there ofcourse are some precautions - when commands are done like that (static) all processing shall be stateless (no properties or fields inside command classes). If this is required - for example to pass some data from DoExtraStuff()
- you just remove static fields and create new instance of command every time the GetCommand()
is being called. Good thing is - the main Processing function remains the same - power of encapsulation.
Delegate approach might also be fine, but I think my classic solution is much easier to expand, extend and maintain.
EDIT 2: I was asked by OP to explain more situation where there is some data to pass between preprocessing function and main function. There are at least two possible solutions.
First is to implement factory other way, so this is no longer using static instances of commands but creates a new one for each call:
public static class CommandFactory2
{
public static BaseCommand GetCommand(string invokedVerb)
{
switch (invokedVerb)
{
case "barcode": return new CommandA();
case "addblankpage": return new CommandB();
// ...
}
return null;
}
}
The only remaining thing is to add some fields / properties to BaseCommand or specific Command and just share values between methods of the class, like:
class CommandC : BaseCommand
{
private string _extraValue = null;
protected override void ExtraStuff()
{
base.ExtraStuff();
_extraValue = ProcessExtraValue();
// I have to set some extra parameters here...
}
protected override void DoRealStuff(DocWrapper doc)
{
// Doing my other stuff here!
switch (_extraValue)
{
// ...
}
}
private string ProcessExtraValue()
{
//...
return "";
}
}
The second approach would be suitable if you always produce same intermediate data, so then you do not alter Factory but rather BaseCommand interface and children, like:
public abstract class BaseCommand
{
protected BaseCommand()
{
}
public void Process(StuffOptions options)
{
Logging.Log("here is a bunch of logging");
// here sometimes there is some action-specific code but not often
ExtraData data = new ExtraData();
ExtraStuff(data);
using (DocWrapper doc = new DocWrapper(options.File)) // this is in all actions
{
foreach (int page in doc.GetPagesToModify(options.Pages)) // this is in most actions
{
// call some stuff on the doc instance
DoRealStuff(doc, data);
}
doc.Save(options.OutputFile); // this is in all actions
}
}
protected abstract void DoRealStuff(DocWrapper doc, ExtraData data);
protected virtual void ExtraStuff(ExtraData data)
{
}
}
And then you just fill this ExtraData
class with whathever proeprties you wan't to share.
I would personally prefer first one, as it is more elastic and introduces extra stuff only when this is required (ie. only inside these commands that need this). However drawback is if you are planning on intense processing - you will be creating new instance of command class every time you are going to use it...
Eventually, this can be solved on factory level - implement each command creation the best way that suits it better - even create deeper command inheritance to distinguish different behavior. But now I went too far (and broke SOLID a bit), so let allow me to finish at this point :P
BarCodeOperations
andPageOperations
both inherit from a baseOperations
class, or is Operations a namespace? \$\endgroup\$options.AddBarcodeVerb
andoptions.AddBlankPageVerb
? Are they constant or do they change according to something? \$\endgroup\$Operations
is just a namespace.options.StuffVerb
are of typeStuffOptions
, and implementIProgramOptions
, an interface describing parameters common to all verbs. Note that when I asked my question, it did not yet implementIProgramOptions
. See more info here: github.com/gsscoder/commandline/wiki/Verb-Commands \$\endgroup\$