0

My questions are specifically dealing with dependency injection through the constructor. I understand the pros/cons of service locator pattern, constructor/setter injection, and their flavors, however there is something I can't seem to get past after choosing pure constructor injection. After reading many materials for testable design, including a thorough perusing of Miško Hevery's blog (specifically this post) I'm at the following situation:

Assume I'm writing a C++ program, and I have correctly injected my dependencies through their constructors. For readability I have given myself a high-level object which has a single Execute() function called from main:

int main(int argc, char* argv[]) {
    MyAwesomeProgramObject object(argc, argv);
    return object.Execute();
}

Execute()'s responsibility is to simply wire up all required objects and kick off the highest level object. The highest level object requires a couple dependencies and those objects required a few objects and so on and so on, implying a function that looks like this:

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();
}

From what I take from Miško's blog (and I would ask him, but figured he wouldn't have time to get back to me), this is the only way to satisfy pure constructor injection of dependencies.

In the blog post mentioned, he states we should have factories on a per object lifetime level, but this is essentially what Execute is doing, making my code look identical to his example:

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);

Questions:

  • Is this the standard approach?
  • Is this a pattern that I'm not aware of (seems similar to Maven's context.xml)?
  • For pure constructor injection, do I simply suffer the cost of "upfront" allocation?
7
  • I'm pretty sure it's not the only way to do it... You could use an IoC container. Commented Nov 27, 2013 at 16:46
  • Edited. But a friend of mine says that this violates all kinds of OO design rules. I couldn't say what specifically he was implying, but it does seem odd to allocate all objects required in the program on program startup.
    – Danny A
    Commented Nov 27, 2013 at 16:49
  • IoC containers do that all the time. Well, at least for the objects that the program needs right now. I would argue that it's overengineering in many cases, but that's probably another discussion. The idea is twofold: to decouple your dependencies, and to catalog them in one location. Commented Nov 27, 2013 at 16:49
  • 1
    There's nothing wrong with using DI containers per se. But I think people reach for them too soon. DI containers are for large, corporate programs that have tons of references, not small or even medium-sized programs where simple constructor injection is perfectly adequate. Commented Nov 27, 2013 at 16:55
  • 3
    @Mike: Unwarranted complexity without a corresponding benefit. The downside of all Architecture Astronaut practices; you run out of oxygen. Commented Nov 27, 2013 at 17:02

1 Answer 1

1

Is this the standard approach?

There is no such thing as a "standard approach", in particular in C++. Really, this kind of choice is specific to the design you need to solve your problems.

Basically, what you need to ask is "when do I need to initialize this dependency?" and you need to ask that for each dependency. If you can intialize everything on construction, then nice, you might avoid unnecessary new/delete calls, which makes your program easier to deal with. If you really need to have some dependencies initialized only on the execution call, then you don't have a choice, do it at this moment.

However, I would suggest avoiding new/delete as much as possible if you can, for example by putting your late-initialization dependencies into another object which would represent their lifetime, maybe called "ExecutionDependencies" or something, managed by a smart pointed (std::unique_ptr is certainly the best choice). It's equivalent to having several new/delete calls but you do only once for the whole object holding all execution dependencies. It's then easy later to release dependencies only needed for execution and keep dependencies needed for the whold program lifetime.

That being said, it might not be very important so frankly I would just write the most simpler way first, then refactor later if it's bothering me. Except if initialization and termination are actually an important problem in your program.

I would avoid factories in general, until you have a very specific need for it, which is not necessary here.

Is this a pattern that I'm not aware of (seems similar to Maven's context.xml)?

A pattern is not a way to implement a solution, it's just an example of design. Don't assume it's a different pattern than Dependency Injection, really it's another way of doing the same.

For pure constructor injection, do I simply suffer the cost of "upfront" allocation?

I'm not sure what you mean by that: allocating everything first is a good way to avoid memory fragmentation. However, it imply that you avoid as many new/delete calls as you can, grouping things together if you want to optimize space and execution time, so my suggestion of grouping dependencies (when they are needed only in the execution call) is spot on if it's your concern.

1
  • sorry for the late reply, but thanks. I think between you and Robert Harvey my stress has eased a bit. Knowing that this is not totally bonkers make me feel more sane for reaching this conclusion.
    – Danny A
    Commented Jan 9, 2014 at 16:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.