C++的战略模式.实施选项
Strategy pattern in C++. Implementation options
这是一个简化的例子,叫做(我希望 - 如果我错了,请纠正我) 策略模式:有一个类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_;
};
可以看出,这种方法的主要缺点是不可靠的 - 传递给FileWriter
的对象Formatter
在整个FileWriter
生命周期内必须存在,因此像FileWriter("test.txt", JsonFormatter())
这样的调用直接导致SegFault
。
在这方面,我想讨论一下实现这种方法的其他选择,这些方法具有"易于使用"和简单性的要求:
- 可以在创建文件编写器时传递新的格式化程序,或者
- 可以传递和使用现有的格式化程序。
我想出了下面描述的几种替代方案及其缺点(IMO):
- 模板:将
FileWriter
作为模板类,将精确FormatterClass
作为参数;缺点:调用起来很难看:FileWriter<JsonFormatter>("test.txt", JsonFormatter())
- 在这里,JsonFormatter
被键入两次。 - 原始指针:
FileWriter("test.txt", new JsonFormatter())
;缺点 - 谁应该删除格式化程序对象?FileWriter
?如果是,则传递现有格式化程序的地址将导致FileWriter
对象尝试删除格式化程序后SegFault
。 - 共享指针:
FileWriter("test.txt", dynamic_pointer_cast<IFormatter*>(shared_ptr<JsonFormatter*>(new JsonFormatter()))
;缺点:调用起来很难看,再说一次,如果在创建文件编写器之前创建了格式化程序怎么办?
这里的最佳实践是什么?
更新
为了响应建议使用std::function
的答案 - 如果格式化程序可以存储状态(例如,精度)并具有其他方法(例如 getHeader()
,例如 CSV 文件,该怎么办?
此外,无法按值存储IFormatter
,因为它是一个抽象类。
最简单的解决方案是使用:
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.
模板:将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());`
这就是标准库所做的(例如 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
,要么把生命周期管理权交给调用者(如果你重视效率而不是安全,这是一种完全有效的方法)。
using IFormatter - std::function<std::string(std::string,double)>;
您的格式化程序应该是一个函数,而不是一个接口。
如果调用方想要保证生存期,可以使用std::ref
,如果他们想要模糊的生存期,可以使用包装共享 ptr,或者按值传递。
如果你想要一个更丰富的接口,你可以拿一堆这样的接口,或者写一个一堆这样的类(通过继承或手动编写notstd::functions
)。
按值存储IFormatter fmt;
,使用fmt(a,b)
而不是fmt.format(a,b)
(DRY! 如果需要,客户端代码可以使其成为引用或智能语义。
继承作为实现细节,而不是驱动你的设计,是自由的。
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- Win32编译器选项和内存分配
- C/C++预处理器是否可以检测一些编译器选项
- 是否有C++编译器选项允许激进地删除所有函数调用,并将参数传递给具有空体的函数
- 将--whole archive链接器选项与CMake和具有其他库依赖项的库一起使用
- 通过选项卡的文本设置QTabWidget顺序
- 通过ccmake在cmake中缓存依赖选项
- 如何传递多个 std::文件系统选项?
- 基于编译器选项的编译二进制路径
- "perf_event_attr"结构的"read_format"属性的选项到底是什么?
- 如何应用 libcurl 的持久连接选项
- 文件中.dat Dlib 选项
- LLVM | codegen 用于带有命令行选项的程序输入功能
- 编译 Boost 时在 OS X 上的"ld:未知选项:-soname"
- 按钮悬停在 QT 中垂直布局的选项卡小部件中不起作用
- Visual Studio C++ 它只构建选项卡中显示的文件吗?
- 在C++不适用于猜数字游戏的情况下再次播放选项
- 提升程序选项 添加选项语法
- 根据第一个选项选择选项组
- 提升计划选项依赖选项