Strategy Pattern
The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Here's some excellent examples of the strategy pattern
As you can see from the 1st link above, you can detect the file type in the header in your main method, and then feed the correct version of the Strategy into the context and then call the context.
If you really want to you could write a Factory Method to automatically create your context.
So you could call something like the following
var context = FileFactory.CreateContext(myFile);
context.Parse();
A factory method might be a little overkill for this however, this would be a judgement call on your part. However using the strategy pattern as above gives you the flexibility to add any number of new file types to parse. Additionally the factory will give you the ability to abstract away all of the polymorphism from your logic code.
Edit
Here is what you would end up with using a strategy pattern combined with a Factory Method.
public interface IFileParserStrategy
{
IList<Person> Parse(String path);
}
public class FileFormatNo1 : IFileParserStrategy
{
private string _path;
public FileFormatNo1(String path){
_path = path;
}
public IList<Person> Parse()
{
// Actual Logic for handling File Format #1
// and returning a IList<Person>
}
}
public class FileFormatNo2 : IFileParserStrategy
{
private string _path;
public FileFormatNo1(String path){
_path = path;
}
public IList<Person> Parse(String path)
{
// Actual Logic for handling File Format 2
// and returning a IList<Person>
}
}
public interface IFileParserContext
{
public IList<Person> Parse();
}
public class FileParserContext : IFileParserContext
{
private IFileParserStrategy _strategy;
public FileParserContext(IFileParserStrategy fileParserStrategy)
{
_strategy = fileParserStrategy;
}
public IList<Person> Parse()
{
return _strategy.Parse();
}
}
public static class FileParserFactory
{
public static IFileParserContext CreateContext(String path)
{
FileParserContext context = null;
var checkFileFormat = File.ReadAllLines();
if(checkFileFormat.Contains("FileFormatNo1"))
context = new FileParserContext(new FileFormatNo1(path));
if(checkFileFormat.Contains("FileFormatNo2"))
context = new FileParserContext(new FileFormatNo2(path));
if(context == null)
throw new FileFormatException("File is not in a readable state");
return context;
}
}
public static void Main()
{
var filePath = "someFile.txt";
var context = FileParserFactory.CreateContext(filePath);
var personList = context.Parse();
}
Update
You could modify the above strategy to pass around a FileStream if you only wanted to open the file once. You could modify FileParserFactory.CreateContext to accept a FileStream for its parameter, and refactor the strategies to accept the FileStream instead of the filePath.
public static void Main()
{
var filePath = "someFile.txt";
List<Person> personList;
using(var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var context = FileParserFactory.CreateContext(fileStream);
personList = context.Parse();
}
}
Example 2 is quite bad for testing... and I don't mean that you can't test the internals. You also can't replace your XmlReader
object by a mock object as you have no object at all.
Example 1 is needlessly hard to use. What about
XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();
which is not any harder to use than your static method.
Things like opening the URL, reading XML, converting bytes to strings, parsing, closing sockets, and whatever, are uninteresting. Creating an object and using it is important.
So IMHO the proper OO Design is to make just the two things public (unless you really need the intermediate steps for some reason). Static is evil.
Best Answer
An object-oriented design may actually be appropriate here, but both your parse methods belong in separate subclasses. You can use generics (type parameters) to achieve this. Something like: