如何最大限度地减少对象构造中的样板文件和耦合?

发布于 2025-01-14 10:35:37 字数 1701 浏览 1 评论 0原文

我有一个 C++20 程序,其中配置通过 JSON 从外部传递。根据“清洁架构”,我想尽快将信息转移到自定义的结构中。 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"]};
}

这样写起来比较安全,因为可以直接看到结构体字段名称与 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;
};

That is very short, will warn me about uninitialized fields, fields and JSON 之间的对应关系是直接可见的。但我需要在 Config.h 文件中导入 JSON 标头,我真的不喜欢这样做。这也意味着如果我应该更改配置加载的方式,我需要重新编译使用 Config 类的所有内容。

当然,C++ 是一种需要大量样板代码的语言。理论上我最喜欢第二种。它是最封装、最分离的一种。但编写和维护起来却是最差的。考虑到在实际代码中字段的数量要大得多,我会牺牲编译时间来减少冗余和提高可维护性。

是否有某种替代方法来组织这一点,或者最分离的变体也是具有最多样板代码的变体?

I have a C++20 program where the configuration is passed externally via JSON. According to the “Clean Architecture” I would like to transfer the information into a self-defined structure as soon as possible. The usage of JSON is only to be apparent in the “outer ring” and not spread through my whole program. So I want my own Config struct. But I am not sure how to write the constructor in a way that is safe against missing initializations, avoids redundancy and also separates the external library from my core entities.

One way of separation would be to define the structure without a constructor:

struct Config {
  bool flag;
  int number;
};

And then in a different file I can write a factory function that depends on the JSON library.

Config make_config(json const &json_config) {
  return {.flag = json_config["flag"], .number = json_config["number"]};
}

This is somewhat safe to write, because one can directly see how the struct field names correspond to the JSON field. Also I don't have so much redundancy. But I don't really notice if fields are not initialized.

Another way would be to have a an explicit constructor. Clang-tidy would warn me if I forget to initialize a field:

struct Config {
  Config(bool const flag, int const number) : flag(flag), number(number) {}

  bool flag;
  int number;
};

And then the factory would use the constructor:

Config make_config(json const &json_config) {
  return Config(json_config["flag"], json_config["number"]);
}

I just have to specify the name of the field five times now. And in the factory function the correspondence is not clearly visible. Surely the IDE will show the parameter hints, but it feel brittle.

A really compact way of writing it would be to have a constructor that takes JSON, like this:

struct Config {
  Config(json const &json_config)
      : flag(json_config["flag"]), number(json_config["number"]) {}

  bool flag;
  int number;
};

That is really short, would warn me about uninitialized fields, the correspondence between fields and JSON is directly visible. But I need to import the JSON header in my Config.h file, which I really dislike. It also means that I need to recompile everything that uses the Config class if I should change the way that the configuration is loaded.

Surely C++ is a language where a lot of boilerplate code is needed. And in theory I like the second variant the best. It is the most encapsulated, the most separated one. But it is the worst to write and maintain. Given that in the realistic code the number of fields is significantly larger, I would sacrifice compilation time for less redundancy and more maintainability.

Is there some alternative way to organize this, or is the most separated variant also the one with the most boilerplate code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

樱花落人离去 2025-01-21 10:35:37

不过,我会采用构造函数方法:

// 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 标头。当然,构造函数作为声明和定义是重复的,但这是通常的 C++ 方式。

I'd go with the constructor approach, however:

// 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"])
{ }

Clean approach and you avoid indirect inclusions of the json header. Sure, the constructor is duplicated as declaration and definition, but that's the usual C++ way.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文