In case I want to use the DIP to develop a hypothetical modular C++ project. Because of the modularity I choose to implement one specific feature completely in one library A
. Another library B
(or two, or three ...) is using this feature (e.g. a logging mechanism):
class ILogger
{
virtual void log(const std::string& s) = 0;
};
Where should I put this interface physically? Some bloggers seem to suggest, that because the interface belongs to its users (because of DIP) you shall put the interface on the user side (or here). This would also improve testability, because you don't need any implementation to be linked to the test.
This would mean, that the library A itself will not compile, because it lacks the interface. It will also mean, that if a library C will also use the logging facility it will also bring in an interface ILogger
, which will break the ODR?! This could be solved by introducing an extra package layer library D which contains only the interface. But the main problem remains:
Where to put the interface? I read the original paper about DIP, but I cannot agree with the interpretation, that I should not put the interfaces into the library. I have the feeling, that the this paper is meant as a guideline of how to think of development (as "the users are defining the interface not the implementators"). Is this correct? How do you use the Dependency Inversion Principle?
The Dependency Inversion Principle is the fifth and final design principle that we discussed in this series. It introduces an interface abstraction between higher-level and lower-level software components to remove the dependencies between them.
Dependency inversion well applied gives flexibility and stability at the level of the entire architecture of your application. It will allow your application to evolve more securely and stable.
Definition: One should depend upon abstractions and not concrete instances. Typically in a software application, high level components need to depend on lower level components. If the low level components are 'hard-coded' into the high level components this causes two problems.
Software can be viewed as a combination of different layers:
One layer is the implementation level (rougthly, the function level)
Another one is the way data structures interact together (the class level, which is primarily where the DIP should apply)
And another one the way that components should interact together (the package layer). We also would like to apply some kind of DIP here if possible. Robert C. Martin insist on the fact that this layer is primarily business-dependent (whatever that means) and so principles are a little different: Stable-Dependencies Principle and Stable-Abstractions Principle (see Martin's Principle Pattern and Practice)
Now what should also be emphathized about principles in software engineering, is that you should apply them only when you have to solve the problem they solve. As long as you don’t have the problem, do not use them.
At the class level, you should use the DIP if you have good reasons to believe that your logging mechanism will be implemented by several classes. If you believe that there will be only one logging mechanim for the time being, then it is perfectly fine not to use the DIP because there is no problem to solve.
Now the same kind of choice should be made at the package level. However, the guide for your packaging choice is deployment. Here:
class ILogger {
virtual void log(const std::string& s) = 0;
};
class A : public ILogger {
…
};
class A2 : public ILogger {
…
};
Anyway, this decision is mostly business dependent. Also remember that packaging should be done bottom-up: only create a new package when you have many classes you want to organize. Until you don’t have this many classes, don’t try to make early decisions because you will almost certainly be wrong.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With