So the final principle that we're going to take a look at is the dependency inversion principle.
Now I'm sure that many of you have heard about dependency injection but dependency inversion principle
isn't really about the injection.
It's simply specifying the best way to form dependencies between different objects so the dependency
inversion principle is actually split into two different ideas.
So here they are.
The first idea is that high level modules should not depend on low level modules and instead both of
them should depend on abstractions.
And the second idea is that abstractions should not depend on the details but rather the details should
depend on abstractions.
So first of all the question what do we mean by abstraction.
Generally when we talk about abstractions we talk about interfaces or base classes.
So essentially instead of depending on a concrete type what you really want is you want to be as general
as possible you want to take in an interface which provides you the necessary functionality but you
don't want to depend on the concrete type more than you need to because then you're depending on the
implementation details.
So for example if some class has a vector inside it you start taking that vector and iterating it you
might be in trouble if the class decides later on to change it to something else.
And that's why we have abstractions in the first place so that you can change the underlying implementation.
Now let's actually take a look at where this could Pop-Pop So let's suppose that we decide to model
relationships between different people.
So I'll have an interim class called relationship and we're going to have different relationships like
for example a parent or a child or a sibling.
So this signifies the relationships between person and person B So person A might be a parent of person
B or their child or their sibling or something.
Well defined the structure called person with just the name and nothing else really.
We don't really care about anything else for now what we might want to do is we might want to make some
sort of low level construct for actually storing the relationships.
So let's have a structure called relationships.
So the structure called relationships will have a vector.
So we're going to have a vector off polls and inside the table we're going to have the two people as
well as the relationship between them so we're going to have person Colma relationship karma relationship
Colma person like so.
And this is going to be the set of relations that we have.
Now what we can do is we can provide some sort of API for actually asing elements to this Relations
collection.
So for example if you want to add a parent than any child it would look something like the following
add parent and child where you'd take the parent and you take the child as well and you simply add them
to the relations.
Now you would have to add them twice because remember we have the parent relationship and also the child
relationship.
So you advise something like relations relations don't push back on here for example you would have
the parent being the parent of a particular child but you would also have the reverse relationship.
So in this case you would have the child which would be the child of the parent like so.
OK so having constructed this kind of definition and by the way it's completely valid please please
ignore the C line squiggly here.
So having constructed this kind of API what we can do is we can start using it straight away.
So for example we can make a parent code Jaun and do children called Matt and Chris and we can build
the relationships out of them.
So I'll have person called what's called this variable parent.
So John is going to be the parent.
And then we're going to have a child one called Chris and child 2 called math for example.
OK so now what we're going to do is we can create an instance of relationships like so and we can add
the actual relationships.
So I can as parent and child where we have parent and child one and the same would go for child to child
to like so.
OK so this is all good and we can now try to perform research on the different relationships.
So essentially the structure called relationships that we have right here is what you would call typically
a low level macho low level module or a low level construct in our design because essentially all it
does is it provides functionality for data storage.
So we have this vector of temples and it has a couple of utility functions for actually populating that
story.
But if you want to perform research on the data this would be a high level module we may as well call
it research.
So we have struct research like so and this would be a high level higher level like so.
Now let's suppose we want to do all the research in the constructor.
So you would have a constructor.
But of course we know that in order to perform the research we need to somehow get the data.
Now the worst thing to do here and the thing which breaks the dependency inversion principle is to actually
take direct dependency on a low level Marjoe And remember if we scroll up here it clearly says high
level modules should not depend on low level mojo but that's exactly what we're going to do.
So we're going to have a high level module called research which is going to depend on a low level module
like this so we're going to take a reference to it.
And with that reference what we're going to do is we're going to actually access the details.
Now remember this is once again something that we should really try to avoid doing.
We don't want to depend on the details as you can see here and the definition of the dependency inversion
principle but we are going to do it just to illustrate how much of a bad idea this is.
So what I could do is I could take a reference to the vector off temples that we have so I can say relationships
Daut relations and I can take a reference and then that.
Now unfortunately the high level module is kind of directly dependent on the implementation details
because we know it's a vector.
So we start traversing this vector.
So for example I can use all the fancy C++ 17:6 X for traversing the top also all have first rail and
second out of the relations.
And I can obviously perform my research so if I want to find all the children of John I can do it I
can say if first name is equal to name and relationship.
Well actually if first name is equal to John for example if I'm looking for children of John and the
relationship is equal to relationship.
Colin Colan parents for example than I know that second is actually John's child so we can print this
out.
We can for example see out the fact that John has a child
called and then put second name in here like so.
OK so despite some of the complaints from sea lion and by the way I'm using C++ latest here so that
should explain why maybe some of this stuff isn't recognized despite some of the complaints.
This is actually valid code and it will work just fine.
So let me make a research component research underscore and it has a dependency on relationships.
So here I would specify that relationships in here and we're generally good to go.
So essentially even though we are breaking the dependency inversion principle we do have something that's
working and I can actually try to compile it and just show you the fact that this produces the result
we want.
So here we have John has a child called Chris and John has a child called Matt So everything is fine
except let's imagine what happens if for example the low level module decides to change the storage
of relations.
Or perhaps it just goes off and it makes the relations member variable private so it tries to hide it
completely and in this case the high level code is completely broken unfortunately and that's why the
dependency inversion principle exists in the first place.
It protects you from changes in the implementation details.
So the question is Well how would you go about fixing this particular situation where you would typically
fix it through an abstraction.
So instead of having the direct dependency on some low level modules you would introduce an abstraction
you would introduce some sort of interface but that's only part of the problem.
The other part of the problem is really if you want to find all the children of a particular person
I would argue that the Single Responsibility Principle states still puts this responsibility as part
of the relationships class.
So your research doesn't really have to delve into you know finding the children of somebody you can
move it down so to speak you can move it into a low level.
So we have two ideas moving something to a low level mojo and introducing an abstraction now that abstraction
is going to be called a Relationship browser relationship browser is going to provide a few useful bits
of functionality for searching for example for all the children of a particular person so we can do
that so we can have a virtual function which returns a vector of person and we're going to call it find
all children of.
And in this case just you just specify the name basically you specify the name of the parent and you
have to find all of the children.
So this is the interface.
You can see it's a pure virtual function and then we take relationships and we specify that relationships
is actually providing functionality of a relationship browser.
So coming down here I can now jump in to implement functions and implement the fine all children all
function now the implementation itself is fairly obvious as I'm sure you may have guessed.
Basically you make a result which is a vector of person or you can make a vector of person pointers
or something like that if you want.
Then once again we iterate through all of the all of the actual items that we have.
So first SEC second relations you once again check whether you found the right person.
So you say if first name is equal to the name that you're looking for and the relationship is relationship
colon colon parents then yeah you can add it to the results or result they'll push back and you add
the second which is the child of John or whoever you're looking for.
And then you return the result.
So we have now effectively conformed relationships to the relationship Bowzer interface by implementing
this particular function.
And as a consequence of this we can rewrite the research component so instead of depending on all these
details it is going to comment everything out we're now going to depend just on a relationship browser.
So research is going to depend on the relationship browser reference's called browser.
And here all you have to do is you just have to use the API that we made because we made this special
function called Find all children off.
So essentially what you can say is something like for auto child in browser Daut find all children off
John for example and we can once again print out the information that John has a child called and once
again we can get the child name followed by an end line so let's actually see what happens as we execute
this.
Interestingly enough we don't have to change any of the code here so the code here is still fine because
remember relationships implements the relationship browser interface and so if we stick it into the
constructor.
Thanks to polymorphism it works just fine.
So we stick a relationships in here as a relationship browser and everything works correctly.
So there's actually execute this and see what we get.
All right so as you can see we're getting exactly the same results but we're getting them in a nicer
fashion so the take away from this is that you want to have dependencies on abstractions.
Meaning typically dependencies on interfaces rather than concrete implementations and you don't want
to delve into other classes details and start depending on their details because remember at some point
in time relationships might decide to replace the vector by some kind of map or something.
And in this case you're going to be really screwed because you're either reading a vector of temples
and there is no longer a vector of tuples for you to iterate.
Now speaking of dependency injection what typically happens is dependency injection allows you to specify
certain defaults.
So for example dependency injection would allow you to specify that whenever somebody wants a relationship
browser they should be provided a copy or a reference to the relationships class.
So this is how the Benas injection kind of plays on top of the dependency inversion principle but the
inversion principle just basically says that both of the high level and low level modules should really
be depending on abstractions and you should not depend on details of somebody else's implementation.