Here's a simplified example of what is called (I hope - please, correct me if I'm wrong) Strategy pattern: there's a class FileWriter
which writes key-value pairs to a file and uses object of IFormatter
interface for formatting text being written. There are different formatters implementations and formatter object is passed when FileWriter
is created.
Here's one (bad) implementation of such pattern:
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <sstream>
using namespace std;
class IFormatter {
public:
virtual string format(string key, double value) = 0;
};
class JsonFormatter : public IFormatter {
public:
string format(string key, double value) {
stringstream ss;
ss << "\""+key+"\": " << value;
return ss.str();
}
};
class TabFormatter : public IFormatter {
public:
string format(string key, double value) {
stringstream ss;
ss << key+"\t" << value;
return ss.str();
}
};
class FileWriter {
public:
FileWriter(string fname, IFormatter& fmt):fmt_(fmt)
{
f_.open(fname.c_str(), ofstream::out);
}
void writePair(string key, double value)
{
f_ << fmt_.format(key, value);
}
private:
ofstream f_;
IFormatter& fmt_;
};
As can be seen, the main drawback of such approach is it's unreliability - Formatter
object passed to FileWriter
has to exist during whole FileWriter
's lifetime, thus calls like FileWriter("test.txt", JsonFormatter())
lead directly to SegFault
.
In this regard, I'd like to discuss what could be the other options for implementing such an approach with "easy-to-use" and simplicity requirements:
- either new formatter can be passed when file writer is created, or
- existing formatter can be passed and used.
I came up with several alternatives described below with their drawbacks (IMO):
- templates: having
FileWriter
as a template class which takes exactFormatterClass
as an argument; drawback: ugly to call:FileWriter<JsonFormatter>("test.txt", JsonFormatter())
- here,JsonFormatter
is typed twice. - raw pointers:
FileWriter("test.txt", new JsonFormatter())
; drawback - who should delete formatter object?FileWriter
? if yes, then passing an address of existing formatter will lead toSegFault
onceFileWriter
object attempts to delete formatter. - shared pointers:
FileWriter("test.txt", dynamic_pointer_cast<IFormatter*>(shared_ptr<JsonFormatter*>(new JsonFormatter()))
; drawback: ugly to call, and again, what if formatter was created before creating file writer?
What would be the best practices here?
UPDATE
In response to answers that suggested to use std::function
- What if Formatter may store a state (say, precision) and have additional methods, like getHeader()
, for instance, for CSV files?
Additionaly, storing IFormatter
by value isn't possible since it's an abstract class.