Looking for help applying SOLID principles to a file I/O problem

Viewed 106

I am trying to learn more about design patterns and principles. I usually understand them in theory but I find myself having a hard time when it comes to applying them to a real-world problem. This is going to be a longer question to give all the relevant context and I would very much appreciate any advice on how to structure this properly.

Currently I am trying to implement a file manager (read and write) for a particular kind of structured file called a TextGrid (which is exported from a different software that I did not write) in Modern C++. This file can come in three different "flavors" (which all store the same information, just use different formats):

  • Short text file
  • Long text file
  • Binary file

All flavors have the same extension. The user would normally not know which file they are dealing with and you have to read the beginning of the file to be able to tell the difference.

Now I understand that it makes sense in general to separate the class TextGrid, which represents the structured data stored in the files, from the I/O. So there would be a class TextGrid and a (static) class TextGridManager.

And here is where I am getting lost. Should I implement the reading and writing in their own classes? Should I implement an abstract class BaseTextGridManager and then derive the specialized classes (which, I think, would be what the Open-closed principle would recommend)? But if so, how would the user know which specialized class to instantiate because you have to read the first few bytes of the file before you know how to parse it?

I could certainly find a way to make it work somehow, but I am looking for advice on how to do it elegantly and by following SOLID principles. Any insights would be much appreciated.

Update:

Thanks to the comments below this question, my current plan is to organize it in the following way:

class TextGrid:
{
  // The representation of the TextGrid data
}

class TextGridManager:
{
public:
   static TextGrid readTextGridFile(const std::string& filename);
   static bool writeTextGridFile(const TextGrid& tg, const std::string& filename, const std::string& format);

private:
   TextGridManager(){};
   static TextGrid BinaryTextGridFactory(std::istream& file);
   static TextGrid ShortTextGridFactory(std::istream& file);
   static TextGrid LongTextGridFactory(std::istream& file);
}

I welcome any constructive criticism and feedback.

1 Answers

For a new format, you will add new writer and let the factory know about it. I am not a C++ guy, that doesn't matter here, however. The syntax is of C# with access modifiers omitted.

interface ITextGridReader {
    TextGrid read(filename);
}

class TextGridReader: ITextGridReader {
    // impl read(...);
}

interface ITextGridWriter {
    bool write(filename, textGrid);
}

class BinaryTextGridWriter: ITextGridWriter {
    // imp write(...);
}

class LongTextGridWriter: ITextGridWriter {
    // imp write(...);
}

// Calling code
ITextGridReader reader = new TextGridReader();
TextGrid textGrid = reader.read(...);
ITextGridWriter writer = Factory.CreateTextGridWriter(format);
writer.write(...);
Related