10

I have many cars in my C++ application all contained withing a RaceTrack.

Each car consists of hundreds of parts. Each part depends on some other part or two.

I've read a lot of SO questions on DI and Mark Seemann's book and it looks like I should not define a Car class just to hold car parts because all car parts will depend on each other and this soup of parts is a car. Am I right?

So when I place all of my racing cars into the RaceTrack there will be no car entities but a lot of car parts depending on each other racing on track?? Am I right?

EDIT:

For ages I was programming Car classes because it was obvious for me that I need a Car class if I am programming a car logic. But with DI it is not so obvious to me. Still wondering is it an idiomatic for DI not to create a Car class if I have no defined role for it?

I mean is it okay to have a SteeringWheel for driving, BoltsAndNuts on wheels for pit crew and all sorts of other funny interfaces without having an instance that represents a car as a whole?

3 Answers 3

24

What good are a bunch of car parts sitting on a race track doing anyone?

If all your car class does is hold car parts it's as useful as a wet bag of parts.

Usage

As the driver, what I want is something I can control. That responds when I ask for speed. That handles like its on rails. That can stop on a dime.

What I want is a car class that is usable. That I can tell to do things without having to think about how the carburetor works. I just think about the gas pedal. How those are connected isn't something I worry about. That's abstraction.

Dependency Injection

Dependency injection has nothing to do with that. When I'm driving the car I'm not thinking about how it was built. As long as it works, I don't care how they put it together.

No, DI is what lets my pit crew quickly swap out my tires for better ones when it starts raining on the track. It's nice to be able to do that without having to hop into a completely different car.

DI is really about following a principle: Separate use from construction.

A car that can install new tires at 90 miles an hour might sound cool but I don't think it's going to win any races with a tire installing contraption on it.

DI is about installing your parts in a way that let's your pit crew get to them. When you use new in the same place you program behavior it's like you're welding the carburetor into place. Sure an acetylene torch could remove it but please consider using nuts and bolts first.

That's what DI is. Sure it's not as easy as newing up something as soon as you realize you want it. Instead you have to write separate code that knows how to build your car. But it sure makes it easier to change things later. And it means you don't have to drag a car assembly plant around the track with you.

Construction

Something, somewhere, has to know if the tires are Goodyear. Where then to put the car construction code? If not the car then the pit crew? No. The track? No. All of those have behavior code. Code that must perform during the race. Constructing the car should happen before the race in a place removed from behavior code. Mark Seemans called this place the Composition Root. Most people call it main.

It's a simple pattern. In main, construct the object graph then call one behavioral method on one object in the object graph. That's it. This is the ONLY place construction and behavior have to be together.

That doesn't mean construction has to be a pile of procedural code all laid out sequentially in main. You are free to use every tool in the language to do construction. Just don't mix it with behavior.

Doing this in the language and not using some DI framework or IoC container is called pure DI. It works very well. Has for a long time. We used to just call it reference passing.

DI tools

What a DI tool buys you is the construction details moved into a different language (xml, json, whatever) that enforces the separation between construction and behavior. If you don't trust your fellow programmers not to use new when they shouldn't that can be appealing.

The draw back is that it's tempting to let the DI tool details spread throughout the code base. Sometimes infecting the code base with proprietary annotations. The examples they provide certainly encourage this. The tool tends to move into the language space until you can't just advertise the job as a Java programming job but as a Java/Spring programming job.

Design Principles

For ages I was programming Car classes because it was obvious for me that I need a Car class if I am programming a car logic. But with DI it is not so obvious to me. Still wondering is it an idiomatic for DI not to create a Car class if I have no defined role for it?

I think you're learning about abstraction and changing how you decide a class is needed. That's good. But that's not about DI. DI doesn't help you decide if you need a car class. DI helps you keep the car from knowing, and therefor caring, if the tires are Goodyear tires. DI helps you keep the track from knowing if the cars are made in Japan.

One of the most fundamental questions in software design is "what knows about what?" That's the main thing a UML diagram shows you. When you new up something you're reaching past it's interface to the concrete thing that you're now tied to. The car now has to know that the tires are Goodyear. Which kinda sucks if Michelin want's to sponsor you.

Avoiding doing that is called following the dependency inversion principle. Formally, a high level module (like the car class) should not directly depend on low level modules (like the GoodyearTire class). It should depend on an abstraction (like a Tire interface).

A way to avoid doing that is called Inversion of control. Here the emphasis is on changing the flow of control. Do the tires move the car or does the car move the tires? Thinking about this the right way allows us to not statically couple the car and tires together. DI is one particular way to follow Inversion of Control.

None of this tells you if you need a car class. If you're programming "car logic" it's nice if there is one place to keep it rather then scattering it every where. Just don't be fooled into thinking car construction logic is the same as car behavior logic so it all has to live in the same place. But if you have no defined roll for a car then you don't need either. Race motorcycles around the track if you like.

I mean is it okay to have a SteeringWheel for driving, BoltsAndNuts on wheels for pit crew and all sorts of other funny interfaces without having an instance that represents a car as a whole?

DI or no DI, it's fine to have an instance that represents a car as a whole, but that instance isn't something I want to know about directly if I don't have to. Give me a car abstraction so I don't have to care if it runs on gas, diesel, or electricity when I use it. That's only something I should care about when I'm building it or maintaining it. It's nice if the code that uses car doesn't have to know or care how it works. "I don't know. I don't want to know."

4
  • It would be cool if you didn't use the car and pit crew metaphor for a question that uses them to represent code. But still, a good answer. Apr 9, 2017 at 11:50
  • 6
    And I always thought Inversion of Control was when you steer left but the car turns right...
    – mkrieger1
    Apr 10, 2017 at 13:29
  • CandiedOrange, so no Car class if it has no meaningful interface and well-defined responsibility? And no Car.h in my project. And colleague looking into my code and wondering is it a car parts e-shop or garage managing application? :) Apr 10, 2017 at 19:02
  • @AntonPetrov "if it looks like a duck and quacks like a duck, but needs batteries you probably have the wrong abstraction". Good names are very important and can be hard to think of but should be changed to reflect the well-defined responsibility and meaningful interface so that they don't come as a surprise. If I read the name then look at the interface and think "that's pretty much what I expected" then you have a good name. Apr 16, 2017 at 15:19
15

it looks like I should not define a Car class just to hold car parts because all car parts will depend on each other and this soup of parts is a car. Am I right?

No.

The point of dependency injection is not at all to discourage dependencies. Quite the opposite.

Dependency injection is about the question: where does the car get its parts from? Where and how are they created, and how does the car get references to them?

Naively, the car would create its parts itself by calling new. This is easy and straightforward, but it is very inflexible. The car has to know everything that is necessary to create the parts, and if you ever want to have two cars with different parts, that logic has to go into the car as well.

With dependency injection, all that logic is taken out of the car and put into a configuration component, which creates all the parts and wires up the references. The fully configured dependencies are injected into the car - and each other.

4
  • 1
    Finally, a sane explanation of dependency injection concept. Apr 9, 2017 at 6:34
  • 1
    I'd say the more common system for the "naive" approach isn't that the car would call new, but that the user of the car class would assign its parts to its properties, and in the meantime the car class would be in an indeterminate state. DI provides a means to mechanically assure class validity. Apr 9, 2017 at 6:46
  • 2
    @whatsisname I can easily use DI to make invalid and half initialized objects. Validation is not a DI responsibility. Nor is being immutable. DI can take on the job of building valid and immutable objects. DI has no way to enforce that those are the only ways those objects are constructed and used. The objects must enforce this. Apr 9, 2017 at 17:08
  • @CandiedOrange: so you can easily use any technique to make something half-assed, so what? Requiring dependencies at the constructor, and making them immutable, isn't a silver bullet, but it is nonetheless a very useful way of preventing many common bad situations. Apr 9, 2017 at 18:53
0

So when I place all of my racing cars into the RaceTrack there will be no car entities but a lot of car parts depending on each other racing on track?? Am I right?

Well. Yes and no. A car entity is still valid to have. And a car is more than the sum of its parts. You need a driver too (for the time being). Many times race cars have names as well, not to mention the Make and Model of the car.

Yes cars are basically a container of parts, but it is this packaging and cooperation of parts that make up something bigger: a car.

So really, no. A car isn't just a random bag of metal and plastic. It's a machine.

Dependency injection is not overkill in this case. Something else has to build the car, which would be a factory class or Builder object. The car shouldn't know how to build itself.

1
  • 2
    You can have DI without factories or builder objects. Simple constructor parameters is a valid method that works for many circumstances. Apr 9, 2017 at 6:49

Your Answer

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

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