My opinion is that you should write whatever you need to write to make the long-term stability and maintainability of the program better. What's the point in avoiding writing 20 lines of code today, if every time you touch something that uses this code or come back to maintain this Class, it costs you an extra 5 minutes or more because you didn't put in the time today?
So the question then becomes "what do you need to write?" I don't think you provide enough information to be able to give a definitive answer, so I'll just list some things that I would think about in making a decision:
1. Do you need to have the Trailer object by itself, now or in the forseeable future?
If so, you have a pretty good argument for making the wrapper around both composited objects, since you already are going to have to wrap one or the other. Might as well be consistent.
2. Is any of the three types (Car, Trailer, CarWithTrailer) in an area of the code that developers go into frequently or look likelly to become so?
If so, be very careful, because the ramifications of choosing the wrong thing can be very costly amortized over every time the code is touched. If not, it may not make any difference what you decide. Just pick a direction and go with it.
3. What's easiest to understand?
Does one approach jump out at you that someone coming behind you would immediately "get" what you're trying to do? Do your teammates have particular biases that might make one approach harder for them to maintain? If all things are equal, pick the solution that imposes the lowest cognitive load.
4. Are there any additional advantages to using one approach over another?
One thing that jumps out at me is that the wrapper idea has a distinct advantage if you write it that your CarWithTrailerWrapper can wrap any car and any trailer into a CarWithTrailer. Then, you wind up writing all your pass-through methods, but you only write them once, rather than once for each Car Class.
This makes your initial investment in writing the pass-through methods much cheaper as you add more Cars and Trailers. It also helps reduce the temptation to couple things too directly to the Volvo or VolvoWithTrailer, while reducing the consequences of coupling to the CarWithTrailerWrapper, since you can do way more with just that one implementation.
Maybe there are distinct advantages you'd get for free by using the extension method, but I don't see them.
OK, so it looks like I've talked myself round to going ahead and filling in the pass-through methods, for non-trivial applications.
I'm not a C++ programmer, so I have no idea what code generation tools are available to you. The IDE I use can generate methods from an Interface, and I can add code templates that would make writing pass-throughs pretty painless. If you don't have access to an IDE that does this, you can actually get pretty far letting Excel write code for you.
I meant to write this as a comment, but it turned out to be too long.
How I know that Foobar
is the only owner? In the old case it's simple. But the problem with DI, as I see it, is as it decouples class from construction of its dependencies, it also decouples it from ownership of those dependencies (as ownership is tied to construction). In garbage collected environments such as Java, that's not a problem. In C++, this is.
Whether you should use std::unique_ptr<Widget>
or std::shared_ptr<Widget>
, that is up to you to decide and comes from your functionality.
Let's assume you have a Utilities::Factory
, which is responsible for the creation of your blocks, such as Foobar
. Following the DI principle, you will need the Widget
instance, to inject it using Foobar
's constructor, meaning inside one of the Utilities::Factory
's method, for example createWidget(const std::vector<std::string>& params)
, you create the Widget and inject it into the Foobar
object.
Now you have a Utilities::Factory
method, which created the Widget
object. Does it mean, the method should me responsible for its deletion? Of course not. It is only there to make you the instance.
Let's imagine, you are developing an application, which will have multiple windows. Each window is represented using the Foobar
class, so in fact the Foobar
acts like a controller.
The controller will probably make use of some of your Widget
s and you have to ask yourself:
If I go on this specific window in my application, I will need these Widgets. Are these widgets shared among other application windows? If so, I shouldn't be probably creating them all over again, because they will always look the same, because they are shared.
std::shared_ptr<Widget>
is the way to go.
You also have an application window, where there is a Widget
specifically tied to this one window only, meaning it won't be displayed anywhere else. So if you close the window, you don't need the Widget
anywhere in your application anymore, or at least its instance.
That's where std::unique_ptr<Widget>
comes to claim its throne.
Update:
I don't really agree with @DominicMcDonnell, about the lifetime issue. Calling std::move
on std::unique_ptr
completely transfers the ownership, so even if you create an object A
in a method and pass it to another object B
as a dependency, the object B
will now be responsible for the resource of object A
and will correctly delete it, when object B
goes out of scope.
Best Answer
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
new
ing 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
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.
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."