Al.*_*.G. 5 c++ configuration dependency-injection decoupling
考虑类之间的以下关系:
int main(int, char**) { | class Window {      | class Layout { | class Box {
    /* Use argc/argv */ |     Layout layout;  |     Box box;   |     int height,
    Window window;      |                     |                |         max_width;
}                       |     bool print_fps; | public:        |
                        |                     |     Layout();  | public:
                        | public:             | };             |     Box (int,int);
                        |     Window ();      |                | };
                        | };                  |                |
我为了简单起见构建了这个结构,实际上还有更多的类.
在main()我获取一些应用程序参数(通过配置文件,数据库,CLI参数).现在我想将这些值传递给所需的对象.
我的问题:哪些是最好/最优雅的方法来"破墙",以便我可以"抛出"配置和谁需要它"抓住"它?
最初,我"打开了一些门",并为Window构造函数提供了所需的一切Window,Layout并且Box.然后,Window给了Layout被需要的一切Layout和Box.等等.
我很快意识到这与它所依赖的依赖注入非常相似,但事实证明,它并不直接适用于我的情况.
在这里,我使用基本类似的bool和int实际上,如果我接受它们作为构造函数参数,我得到上面描述的结果 - 一个非常长的类似调用链:Window(box_height, box_max_width, window_print_fps).
如果我想改变什么的类型Box::height来long?我需要遍历链中每个类的每一对标题/源来改变它.
如果我希望我的课程被隔离(我这样做),那么Window不应该担心Box和main不应该担心布局.
然后,我的第二个想法出现了:创建一些类似JSON的结构,它充当配置对象.每个人都得到一个(共享)指针,只要他们想要,他们说this->config["box"]["height"]- 每个人都很高兴.
这有点工作,但这里有两个问题:没有类型安全性 
和类()与整个代码库之间的紧密耦合.Config
基本上,我看到了解决问题的两种方法:
无论是向上还是向下 - 我试图开箱即用(事实上,这是一条线 - 只是↑或↓)但我最终只在这里.
我之前提出的两个想法的另一个问题是关于解析配置的方式:
int height到Box的话,就需要了解箱,以便正确解析值,对不对?(紧耦合)另一方面,如果main.cpp不知道Box(理想情况下),它应该如何将值存储在友方框中?
构造函数中不应该需要可选参数=>不应该破坏应用程序.也就是说,main应该接受缺少某些参数,但是它还需要知道在使用所需参数构造之后必须为所需对象调用setter.
整个想法是努力实现这三个原则:
我实施了一个次优解决方案,我将发布为自我回答,现在效果很好,它总比没有好,但我期待更好的东西!
I decided that I prefer the first approach more, due to the type safety, also I didn't realy like the idea of every every object holding a pointer to some Config.
I did also realize that my perception about DI was not quite correct, or at least that there's a better way to implement it in my case.
So, instead of accepting all parameters of all objects down the tree, a constructor only receives the direct & required dependencies. This entirely solves the duplication issue of 1.
This leaves us only with the tight coupling problem - main has to know about virtually everyone. To know how to create a Window, it needs to create a Layout, and Box, and everybody else.
Firstly, I wanted to take this problem somewhere else, so I made a class to create objects for me. Like a factory, but only used to hide dirty stuff from main.
In a ConfigFactory now I store all parameters in some way and pass them when I need to construct an object. It is irrelevant how configuration is stored, I decided to have a simple struct Data and used PIMPL so that no recompilation of any ConfigFactory.h-dependent file  is needed when parameter logic is changed (that's supposed to happen quite often)
Next, I saw how this guy used templates to make a Factory more generic: /sf/answers/1886531811/, but instead of working with base and derived classes, pointers and dynamic allocation, I defined a member function which returns a stack-allocated object (ideally):
template <typename T> T produce() const { return {}; }
If there's no specialization of produce for the desired type, T is default constructed - this way it's more flexible when adding/removing specializations.
I keep ConfigFactory.h only this small and free of any #includes, so that I do not birng any unneeded dependencies to those who include it.
Now, if I need to have something produced(), I include ConfigFactory.h and declare a specialization for it. I put its definition in some source file (ConfigFactory.cpp) and produce() the object using the parameters in this->data:
main.cpp:
#include "ConfigFactory.h"
#include "Window.h"
template <> Window ConfigFactory::process() const;
int main (int argc, const char** argv) {
    ConfigFactory factory{argc, argv};
    Window window = factory.produce<Window>();
}
ConfigFactory.cpp, Window.cpp or whoever knows how to make a Window:
template <> Layout ConfigFactory::produce() const;
template <> Window ConfigFactory::produce() const
{
    Window window{produce<Layout>()}; // required dependencies
    window.setPrintFps(data->print_fps); // optional ones
    return window;
}
produce() definitions in the corresponding source files, I have some bad feeling about it - because I define things not declared in the header.  ConfigFactory.h for the definitions, it gets quite long and heavy as it needs to know about every produce()able class.Currently I'm using both and in general it works, but it's not perfect.