我负责重构一些解析类似(但不同的文件)的代码。它们的不同之处在于它们具有不同数量的列。我们假设文件类型称为MODEL_FILE
和COMPANY_FILE
。
MODEL_FILE
具有以下格式:
CAR_MODEL CAR_COMPANY MILEAGE
COMPANY_FILE
具有以下格式:
CAR_COMPANY MILEAGE
解析MODEL_FILE
的结果是std::map<Car_Model, std::map<Car_Company, double> >
;解析COMPANY_FILE
的结果将是std::map<Car_Company, double>
。
头文件是这样的:
typedef std::map<Car_Model, std::map<Car_Company, double> > Model_Data;
typedef std::map<Car_Company, double> Company_Data;
struct Data
{
Model_Data data_model;
Company_Data data_company;
};
bool parse_company_file(const std::string& path, Company_Data& data); // 1
bool parse_model_file(const std::string& path, Model_Data& data); // 2
bool parse_generic_file(bool is_company_file, const std::string& path, Data& data); // 3
解析代码确实在3
中。 1
和2
都在内部调用3
,它知道(通过布尔参数)它要解析的文件是2列还是3列。 Data
中只有一个字段会被填充(哪个字段取决于bool参数)。然后,调用3
的函数将从填充的Data
结构中检索结构的相应字段,并用于填充已传递的映射。
通过这种方式,只在一个地方(3
)解析文件的代码。从外部来看,代码很好(两个不同的入口点返回适当的数据),但内部实现并不适合我(使用结构作为使用单个方法的方法)可能填充两种不同且独立的对象类型。)
我想过使用继承,因此泛型方法接收一个指向公共基类的指针,该基类有两个方法(add_model_data()
和add_company_data()
)。它会根据bool
参数调用其中一个或另一个。然而,这更复杂和令人困惑,并且意味着通过让基类知道低级类的方法,它容易出错等来打破抽象。
问题是,是否有可能以某种方式将解析逻辑保留在一个地方,但使用与struct
不同(并且可以说更好)的方法来处理不同的文件?
答案 0 :(得分:1)
std::variant
和boost::variant
是为“或”类型设计的 - A或B类型。这是一种方法。
另一种更为高级的方法是记住有3个数字 - 0,1和无穷大。
这种方法更难,但重构您的解析代码非常通用。我不打算使用这个解决方案,所以我只是在下面草绘它,但是一旦你编写它就可以让你用这个格式的4或20列版本加上最少的工作。
列解析器接受一个字符串并返回一个类型为T的值:
std::string -> T
或
template<class T>
using column_parser = std::function<T(std::string)>;
(我们可以在以后提高效率。)
给定N列解析器,我们可以构建map<T0, map<T1, map<T2, map<..., map<TN-2, TN-1>...>>>>
。
template<class T0, class...Ts>
struct nested_map {
using type=T0;
};
template<class T0, class...Ts>
using nested_map_t = typename nested_map<T0, Ts...>::type;
template<class T0, class T1, class...Ts>
struct nested_map<T0, T1, Ts...> {
using type=std::map<T0, nested_map_t<T1, Ts...>>;
};
这让我们可以选择一组类型并生成地图。
template<class...Ts>
nested_map_t<Ts...> parse_file(std::string path, column_parser<Ts...> columns);
将任意数量的列解析为嵌套映射。
你暴露:
bool parse_company_file(const std::string& path, Company_Data& data) {
column_parser<Car_Company> company = // TODO
column_parser<double> milage = // TODO
try {
data = parse_file( path, company, milage );
} except (some_error) {
return false;
}
return true;
}
bool parse_model_file(const std::string& path, Model_Data& data) {
column_parser<Car_Model> model = // TODO
column_parser<Car_Company> company = // TODO
column_parser<double> milage = // TODO
try {
data = parse_file( path, model, company, milage );
} except (some_error) {
return false;
}
return true;
}
现在,要编写parse_file
,我们会执行类似(伪代码)
template<class...Ts>
nested_map_t<Ts...> parse_file(std::string path, column_parser<Ts...> columns) {
nested_map_t<Ts...> retval;
auto f = open_file(path);
for( std::string line: get_lines(f)) {
std::vector<std::string> column_data = split_into_columns(line);
if (sizeof...(Ts) != column_data.size()) throw some_error;
index_upto<sizeof...(Ts)>()([&](auto...Is){
recursive_insert(retval, columns(column_data[Is])...);
});
}
return retval;
}
其中index_upto
为this in C++14或由手动包扩展和辅助函数替换,而recursive_insert(m, t0, ts...)
采用“嵌套映射”M& m
和一堆元素{ {1}}并递归执行T const&
,直到有1个元素且recursive_insert(m[t0], ts...)
为止。