如何在 object 结构中最小化样板文件和耦合?
How can I minimize both boilerplate and coupling in object construction?
我有一个 C++20 程序,其中配置通过 JSON 从外部传递。根据“Clean Architecture”,我想尽快将信息转换为 self-defined 结构。 JSON 的用法仅在“外环”中很明显,并没有遍及我的整个程序。所以我想要我自己的 Config
结构。但我不确定如何以一种安全的方式编写构造函数,以防止丢失初始化、避免冗余并将外部库与我的核心实体分开。
分离的一种方法是定义没有构造函数的结构:
struct Config {
bool flag;
int number;
};
然后在另一个文件中,我可以编写一个依赖于 JSON 库的工厂函数。
Config make_config(json const &json_config) {
return {.flag = json_config["flag"], .number = json_config["number"]};
}
这样写还是比较安全的,因为可以直接看到struct字段名是如何对应JSON字段的。我也没有那么多冗余。但是我真的没有注意到字段是否未初始化。
另一种方法是使用显式构造函数。如果我忘记初始化字段,Clang-tidy 会警告我:
struct Config {
Config(bool const flag, int const number) : flag(flag), number(number) {}
bool flag;
int number;
};
然后工厂将使用构造函数:
Config make_config(json const &json_config) {
return Config(json_config["flag"], json_config["number"]);
}
我现在只需指定字段名称五次。而在工厂函数中,对应关系并不清晰可见。 IDE肯定会显示参数提示,但感觉很脆弱。
一种非常紧凑的编写方式是使用一个采用 JSON 的构造函数,如下所示:
struct Config {
Config(json const &json_config)
: flag(json_config["flag"]), number(json_config["number"]) {}
bool flag;
int number;
};
真的很短,会警告我未初始化的字段,字段与 JSON 之间的对应关系是直接可见的。但是我需要在我的 Config.h
文件中导入 JSON header,我真的很不喜欢。这也意味着如果我要更改配置的加载方式,我需要重新编译使用 Config
class 的所有内容。
当然,C++ 是一种需要大量样板代码的语言。从理论上讲,我最喜欢第二种变体。它是最封闭的,最分离的。但是编写和维护是最糟糕的。考虑到实际代码中字段的数量要大得多,我会牺牲编译时间来减少冗余和提高可维护性。
是否有其他组织方式,或者最分离的变体也是样板代码最多的变体?
我会采用构造方法,但是:
// header, possibly config.h
// only pre-declare!
class json;
struct Config
{
Config(json const& json_config); // only declare!
bool flag;
int number;
};
// now have a separate source file config.cpp:
#include "config.h"
#include <json.h>
Config::Config(json const& json_config)
: flag(json_config["flag"]), number(json_config["number"])
{ }
简洁的方法,您可以避免 json header 的间接包含。当然,构造函数被复制为声明和定义,但这是通常的 C++ 方式。
我有一个 C++20 程序,其中配置通过 JSON 从外部传递。根据“Clean Architecture”,我想尽快将信息转换为 self-defined 结构。 JSON 的用法仅在“外环”中很明显,并没有遍及我的整个程序。所以我想要我自己的 Config
结构。但我不确定如何以一种安全的方式编写构造函数,以防止丢失初始化、避免冗余并将外部库与我的核心实体分开。
分离的一种方法是定义没有构造函数的结构:
struct Config {
bool flag;
int number;
};
然后在另一个文件中,我可以编写一个依赖于 JSON 库的工厂函数。
Config make_config(json const &json_config) {
return {.flag = json_config["flag"], .number = json_config["number"]};
}
这样写还是比较安全的,因为可以直接看到struct字段名是如何对应JSON字段的。我也没有那么多冗余。但是我真的没有注意到字段是否未初始化。
另一种方法是使用显式构造函数。如果我忘记初始化字段,Clang-tidy 会警告我:
struct Config {
Config(bool const flag, int const number) : flag(flag), number(number) {}
bool flag;
int number;
};
然后工厂将使用构造函数:
Config make_config(json const &json_config) {
return Config(json_config["flag"], json_config["number"]);
}
我现在只需指定字段名称五次。而在工厂函数中,对应关系并不清晰可见。 IDE肯定会显示参数提示,但感觉很脆弱。
一种非常紧凑的编写方式是使用一个采用 JSON 的构造函数,如下所示:
struct Config {
Config(json const &json_config)
: flag(json_config["flag"]), number(json_config["number"]) {}
bool flag;
int number;
};
真的很短,会警告我未初始化的字段,字段与 JSON 之间的对应关系是直接可见的。但是我需要在我的 Config.h
文件中导入 JSON header,我真的很不喜欢。这也意味着如果我要更改配置的加载方式,我需要重新编译使用 Config
class 的所有内容。
当然,C++ 是一种需要大量样板代码的语言。从理论上讲,我最喜欢第二种变体。它是最封闭的,最分离的。但是编写和维护是最糟糕的。考虑到实际代码中字段的数量要大得多,我会牺牲编译时间来减少冗余和提高可维护性。
是否有其他组织方式,或者最分离的变体也是样板代码最多的变体?
我会采用构造方法,但是:
// header, possibly config.h
// only pre-declare!
class json;
struct Config
{
Config(json const& json_config); // only declare!
bool flag;
int number;
};
// now have a separate source file config.cpp:
#include "config.h"
#include <json.h>
Config::Config(json const& json_config)
: flag(json_config["flag"]), number(json_config["number"])
{ }
简洁的方法,您可以避免 json header 的间接包含。当然,构造函数被复制为声明和定义,但这是通常的 C++ 方式。