Мои вопросы конкретно касаются внедрения зависимостей через конструктор. Я понимаю плюсы и минусы шаблона поиска сервисов, внедрения конструкторов / сеттеров и их разновидностей, однако есть кое-что, чего я не могу избежать после выбора чистого внедрения конструкторов. После прочтения многих материалов для тестируемого дизайна, в том числе тщательного изучения блога Мишко Хевери (особенно этот пост) Я в следующей ситуации:
Предположим, я пишу программу на C ++ и правильно ввел свои зависимости через их конструкторы. Для удобства чтения я предоставил объект высокого уровня, который имеет единственную функцию Execute (), вызываемую из main:
int main(int argc, char* argv[]) {
MyAwesomeProgramObject object(argc, argv);
return object.Execute();
}
Функция Execute () состоит в том, чтобы просто подключить все необходимые объекты и запустить объект самого высокого уровня. Для объекта самого высокого уровня требуется пара зависимостей, а для этих объектов требуется несколько объектов и т. Д. И т. Д., Что подразумевает функцию, которая выглядит следующим образом:
MyAwesomeProgramObject::Execute() {
DependencyOne one;
DependencyTwo two;
DependencyThree three;
MidLevelOne mid_one(one);
MidLevelTwo mid_two(two, three);
// ...
MidLevelN mid_n(mid_dependencyI, mid_dependencyJ, mid_dependencyK);
// ...
HighLevelObject1 high_one(mid_one, mid_n);
HighLevelObject2 high_two(mid_two);
ProgramObject object(high_one, high_two);
return object.Go();
}
Из того, что я взял из блога Мишко (и я бы спросил его, но подумал, что он не успеет вернуться ко мне), это единственный способ удовлетворить чисто конструкторское внедрение зависимостей.
В блоге упомянутый, он заявляет, что у нас должны быть фабрики на уровне жизни каждого объекта, но это, по сути, то, что делает Execute, поэтому мой код выглядит идентично его примеру:
AuditRecord audit = new AuditRecord();
Database database = new Database(audit);
Captcha captcha = new Captcha();
Authenticator authenticator =
new Authenticator(database, captcha, audit);
LoginPage = new LoginPage(audit, authenticator);
Обратите внимание, что ваши разные примеры противоречивы. Сначала вы показываете создание объектов в стеке, а ваш последний пример размещает объекты.
Объекты в стеке несколько опасны, но в большинстве случаев просто отлично. Основная проблема состоит в том, чтобы дать другому объекту, который имеет более длительное время жизни, чем ваша функция, указатель на объект, когда этот объект поступает из стека … когда этот долгоживущий объект пытается получить доступ к объекту в стеке после возврата функции, у вас есть проблема. Если все объекты имеют время жизни стека, то все в порядке.
Лично я начал использовать общие указатели, и я считаю, что это является окончательным для облегчения управления большим количеством объектов.
std::shared_ptr<foo> foo_object(new foo);
std::shared_ptr<blah> foo_object(new blah(foo));
Таким образом, blah может хранить копию общего указателя foo навсегда, и все работает, как ожидается, даже между границами функций. Мало того, что общий указатель имеет значение NULL при создании и автоматически удаляется при удалении (когда последний общий указатель удаляется, конечно.) И вы можете использовать слабый указатель в объектах, для которых указатель не всегда должен быть установлен. ..
В противном случае, я думаю, что то, что вы пытаетесь сделать, работает в определенной степени. В моем мире часто вещи создаются позднее, поэтому мне нужен сеттер. Однако, инъекции конструктора очень полезны, чтобы заставить пользователя правильно инициализировать ваш объект (т.е. я часто создаю объекты только для чтения, без сеттеров, которые на 100% инициализируются при построении, очень практично, очень безопасно!)
Других решений пока нет …