Вот упрощенный пример того, что называется (надеюсь — поправьте меня, если я ошибаюсь) паттерном Стратегия: есть класс FileWriter
, который записывает пары ключ-значение в файл и использует объект IFormatter
интерфейс для форматирования записываемого текста. Существуют разные реализации средств форматирования, и объект форматирования передается при создании FileWriter
. Вот одна (плохая) реализация такого шаблона:
#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_;
};
Как видно, основным недостатком такого подхода является его ненадежность - Formatter
объект, переданный FileWriter
, должен существовать в течение всего времени жизни FileWriter
, поэтому вызовы типа FileWriter("test.txt", JsonFormatter())
ведут непосредственно к SegFault
.
В связи с этим я хотел бы обсудить, какие могут быть другие варианты реализации такого подхода с требованиями «простоты в использовании» и простоты:
- либо новый форматер может быть передан при создании файла записи, либо
- существующий форматер может быть передан и использован.
Я придумал несколько альтернатив, описанных ниже, с их недостатками (IMO):
- шаблоны: наличие
FileWriter
в качестве класса шаблона, который принимает точное значениеFormatterClass
в качестве аргумента; недостаток: некрасиво называть:FileWriter<JsonFormatter>("test.txt", JsonFormatter())
- здесьJsonFormatter
набирается дважды. - необработанные указатели:
FileWriter("test.txt", new JsonFormatter())
; недостаток — кто должен удалять объект форматирования?FileWriter
? если да, то передача адреса существующего средства форматирования приведет кSegFault
после того, какFileWriter
объект попытается удалить средство форматирования. - общие указатели:
FileWriter("test.txt", dynamic_pointer_cast<IFormatter*>(shared_ptr<JsonFormatter*>(new JsonFormatter()))
; недостаток: некрасиво вызывать, и опять же, что, если средство форматирования было создано до создания средства записи файлов?
Каковы были бы лучшие практики здесь?
ОБНОВЛЕНИЕ
В ответ на ответы, которые предложили использовать std::function
- Что, если Formatter может хранить состояние (скажем, точность) и иметь дополнительные методы, такие как getHeader()
, например, для файлов CSV?
Кроме того, сохранение IFormatter
по значению невозможно, так как это абстрактный класс.