以下是所谓的简化示例(我希望 - 如果我错了,请纠正我)策略模式:有一个类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
的{{1}}对象必须在整个FileWriter
期间存在生命周期,因此像FileWriter
这样的调用直接导致FileWriter("test.txt", JsonFormatter())
。
在这方面,我想讨论以“易于使用”和简单的要求实施这种方法的其他选择:
我想出了下面描述的几种替代方案(IMO):
SegFault
作为模板类,将精确的FileWriter
作为参数; 缺点:丑陋的呼叫:FormatterClass
- 此处,FileWriter<JsonFormatter>("test.txt", JsonFormatter())
输入两次。JsonFormatter
; 缺点 - 谁应该删除格式化程序对象? FileWriter("test.txt", new JsonFormatter())
?如果是,那么一旦FileWriter
对象尝试删除格式化程序,则传递现有格式化程序的地址将导致SegFault
。FileWriter
; 缺点:难以调用,再次,如果在创建文件编写器之前创建了formatter会怎么样?这里最好的做法是什么?
更新
回答建议使用FileWriter("test.txt", dynamic_pointer_cast<IFormatter*>(shared_ptr<JsonFormatter*>(new JsonFormatter()))
的答案 - 如果Formatter可以存储状态(例如,精确度)并且还有其他方法,例如std::function
,对于CSV文件会怎么样?
另外,由于它是一个抽象类,因此无法按值存储getHeader()
。
答案 0 :(得分:2)
最简单的解决方案是使用:
JsonFormatter formatter;
FileWriter writer("test.txt", formatter);
// Use writer.
另一个更好的选择是clone()
中有一个IFormatter
函数。然后,FileWriter
可以克隆该对象,获取克隆的所有权并在其析构函数中删除它。
class IFormatter {
public:
virtual string format(string key, double value) = 0;
virtual IFormatter* clone() const = 0;
};
class FileWriter {
public:
FileWriter(string fname, IFormatter const& fmt):fmt_(fmt.clone())
{
f_.open(fname.c_str(), ofstream::out);
}
~FileWriter()
{
delete fmt_;
}
void writePair(string key, double value)
{
f_ << fmt_->format(key, value);
}
private:
ofstream f_;
IFormatter* fmt_;
};
现在,您也可以使用临时对象调用FileWriter
。
FileWriter writer("test.txt", JsonFormatter());
// Use writer.
答案 1 :(得分:1)
templates:将FileWriter作为模板类,将FormatterClass作为参数;缺点:难看调用:FileWriter(“test.txt”,JsonFormatter()) - 这里,JsonFormatter被输入两次。
更多模板!
template<class Formatter>
FileWriter<Formatter> makeFileWriter(const std::string& filename, const Formatter& formatter)
{return FileWriter<Formatter>(filename, formatter);}
塔达!现在它很简单:
auto fileWriter = makeFileWriter("test.txt", JSonFormatter());`
答案 2 :(得分:1)
这是标准库的作用(例如std::shared_ptr
可以删除)。 Formatter
必须是可复制的,显然表达式f << fmt(key, value)
必须格式正确。
class FileWriter {
public:
template<typename Formatter>
FileWriter(std::string fname, Formatter fmt) :
fmt(fmt)
{
f.open(fname.c_str(), std::ofstream::out);
}
void writePair(std::string key, double value)
{
f << fmt(key, value);
}
private:
std::ofstream f;
std::function<std::string (std::string, double)> fmt;
};
如果界面中需要多个函数,则可以使用原始方法,但使用std::unique_ptr
或std::shared_ptr
控制格式化程序的生命周期(请记住使析构函数为虚拟)。
struct Formatter
{
virtual ~Formatter() {}
virtual std::string format(std::string key, double value) = 0;
};
class FileWriter {
public:
FileWriter(std::string fname, std::unique_ptr<Formatter>&& fmt_)
{
if (!fmt_)
{
throw std::runtime_error("Formatter cannot be null");
}
f.open(fname.c_str(), std::ofstream::out);
fmt = std::move(fmt_); // strong exception safety guarantee
}
void writePair(std::string key, double value)
{
f << fmt->format(key, value);
}
private:
std::ofstream f;
std::unique_ptr<Formatter> fmt;
};
如果要将现有Formatter
传递给FileWriter
,则需要将其复制/移动到智能指针以转移所有权,或者需要将其包装在格式化程序界面中。
class FormatterProxy : public Formatter
{
public:
FormatterProxy(Formatter& fmt) :
fmt(fmt)
{
}
std::string format(std::string key, double value)
{
return fmt.format(key, value);
}
private:
Formatter& fmt;
};
这仍然存在您要避免的生命周期管理问题。
但是,我没有看到任何解决方法。您可以将Formatter
的唯一或共享所有权提供给FileWriter
,或者将生命周期管理留给来电者(如果您重视效率而不是安全,这是一种非常有效的方法。)
答案 3 :(得分:0)
using IFormatter - std::function<std::string(std::string,double)>;
您的格式化程序应该是一个函数,而不是一个接口。
如果用户希望保证生命周期,则可以使用std::ref
,如果他们想要模糊的生命周期,或者通过按值传递,则可以使用共享ptr。
如果你想要一个更丰富的界面,你可以采取一堆这样的,或者写一堆这样的类(通过继承或手动编写notstd::functions
)。
存储IFormatter fmt;
按值,使用fmt(a,b)
代替fmt.format(a,b)
(DRY!)。如果需要,客户端代码可以使其成为ref或智能语义。
继承作为一个实现细节,而不是驱动你的设计,正在释放。