By using reflection and assembly loading, we can create robust plugin-based architectures. It can be very very robust (we can check that a class implements a specific interface, then search for its methods, then even check that the parameters are ok...), but in some scenarios that performance is vital this might not be the most optimum approach.
So here it is a very very simple interface based versioning of objects (even compatible any .NET version), so you can maintain compatibility (for example in web services exposed to third parties) while avoiding too much code.
First of all let's define a basic interface (for the 1.0 version) of a simple text logger:
public interface ITextLogger
{
void OutputText(string Text);
}
And we implement both a 1.0 and a 1.1 version (that actually do the same, just to see how when using it the highest version is called):
public class TextLogger10 : ITextLogger
{
public void OutputText(string Text)
{
Console.WriteLine("v:1.0 Text: {0}", Text);
}
}
public class TextLogger11 : ITextLogger
{
public void OutputText(string Text)
{
Console.WriteLine("v:1.1 Text: {0}", Text);
}
}
Then, a request comes to make another logging method, so we define a new interface:
public interface ITextLogger20 : ITextLogger
{
void OutputMoreText(string Text);
}
And the new class, let's increase it to 2.0 to match the highest interface version:
public class TextLogger20 : ITextLogger20
{
public void OutputText(string Text)
{
Console.WriteLine("v:2.0 Text: {0}", Text);
}
public void OutputMoreText(string Text)
{
Console.WriteLine("v:2.0 MoreText: {0}", Text);
}
}
Note than although C# supports multiple interface inheritance, I prefer to make the interfaces inherit previous ones to keep a simple inheritance at class level. This way if a junior developer joins the team he can easily understand how to increment versions.
Finally, to have flexibility (like defining the version to use in a .config file), let's make a very simplified implementation of a Factory design pattern (I like using enums to force people to use only existing ones and avoid errors, but you could change it to a simple string parameter):
public enum TexTLoggerVersions
{
Version10 = 1,
Version11 = 2,
Version20 = 3
}
public static class TextLoggerFactory
{
public static ITextLogger GetInstance(TexTLoggerVersions TextLoggerVersion)
{
switch (TextLoggerVersion)
{
case TexTLoggerVersions.Version10:
return new TextLogger10();
case TexTLoggerVersions.Version11:
return new TextLogger11();
case TexTLoggerVersions.Version20:
return new TextLogger20();
default:
return null;
}
}
}
And now we can build a simple console application to test how this all works...
{
static void Main(string[] args)
{
ITextLogger myTextLog;
myTextLog = TextLoggerFactory.GetInstance(TexTLoggerVersions.Version10);
myTextLog.OutputText("Log 001");
myTextLog = TextLoggerFactory.GetInstance(TexTLoggerVersions.Version11);
myTextLog.OutputText("Log 002");
myTextLog = TextLoggerFactory.GetInstance(TexTLoggerVersions.Version20);
myTextLog.OutputText("Log 003");
((ITextLogger20)myTextLog).OutputMoreText("Log 004");
ITextLogger20 myOtherTextLog = (ITextLogger20)TextLoggerFactory.GetInstance(TexTLoggerVersions.Version20);
myOtherTextLog.OutputText("Log 005");
myOtherTextLog.OutputMoreText("Log 006");
Console.ReadLine();
}
}
This would be the output of running the console app:
v:1.0 Text: Log 001
v:1.1 Text: Log 002
v:2.0 Text: Log 003
v:2.0 MoreText: Log 004
v:2.0 Text: Log 005
v:2.0 MoreText: Log 006
The console application shows two ways of handling higher versions, one by casting to the higher interface when calling the new methods, another by casting when obtaining the instance.
I prefer the second one because you get Intellisense of the higher version ;)
I could surely be improved but this was done in less than 10 minutes and even a monkey could use it!
Tags: Development