I’m working on legacy code that looks very similar to this:
public class BaseParser
{
public BaseParser(Stream stream)
{
// Do something with stream
}
}
public class JsonParser : BaseParser
{
public JsonParser(Stream stream)
: base(stream)
{
// Do something
}
}
public class XmlParser : BaseParser
{
public XmlParser(Stream stream, string someOtherParam)
: base(stream)
{
// Do something
}
}
We have an application that parses incoming files using a particular parser. To determine which type we need to instantiate, there’s a large IF/ELSE block in a factory method:
public static BaseParser Create(string type)
{
if (type == "xml")
return new XmlParser(new FileStream("path", FileMode.Open), "test");
if (type == "json")
return new JsonParser(new FileStream("path", FileMode.Open));
// more...
return null;
}
Since there are a large number of parsers that reside in a single assembly (lets call it Demo.dll), I’d like to split each of these parsers into their own assembly. The “core” classes such as the BaseParser will remain in Demo.dll, but the other parsers and any dependencies will live in Demo.Json.dll, Demo.Xml.dll, etc….
The “core” library will load these assemblies at runtime.
string[] paths = Directory.GetFiles(Environment.CurrentDirectory, "Demo.*.dll");
foreach (string path in paths)
Assembly.LoadFrom(path);
List<BaseParser> parsers = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => x.IsSubclassOf(typeof(BaseParser)))
.Select(x => (BaseParser) Activator.CreateInstance(x))
.ToList();
The problem starts here. The code above will not work since each of the legacy parsers do not have a parameterless constructor. Also, I can’t think of a good way to replace the IF/ELSE block and let each parser determine which file it can handle. I was thinking about possibly adding a virtual method on the base parser:
public virtual bool ShouldHandle(string type)
{
return false;
}
… and each derived parser will override this virtual method. However, there’s two problems:
-
No parameterless constructor and each class’s constructor can have a different number of parameters. This isn’t something I can change since these legacy classes are used everywhere.
-
I have to instantiate the class before I can call the
ShouldHandlemethod. This poses a problem for classes that read from the stream in the constructor.
Is there an alternative route I can take to split these parsers into their own assemblies?
EDIT:
This is a .NET 3.5 application. No MEF unfortunately.
I’ve found that using an abstract factory class can help in these situations.
Each assembly has a factory that is specific to creating the classes that reside in that assembly.
In effect you then have a factory create a concrete factory in order to create your concrete parser.
By doing it this way you can also put logic in place whereby you check for the presence of an assembly before attempting to create the concrete factory associated with it.
I know that it doesn’t get around the
if elsestructure (although that can be replaced with aswitch, passing in a parameter for each class, but it does allow you to break it up and make it easier to handle and support.