What effect does the dependency inversion principle have on the project structure?

In case I want to use DIP to develop a hypothetical modular project in C ++. Due to the modularity, I decided to implement one specific function completely in one A library. Another B library (or two or three ...) uses this function (for example, the registration mechanism):

 class ILogger { virtual void log(const std::string& s) = 0; }; 

Where should I put this interface physically? Some bloggers seem to offer it because the interface is owned by its users (due to DIP) you should put the interface on the user side (or here ). It would also improve testability, because you do not need any implementation for binding to the test.

This would mean that library A itself would not compile because it lacks an interface. It also means that if the C library also uses the ILogger , it will also lead to an ILogger interface that will break ODR ?! This can be solved by introducing an additional package layer D library, which contains only the interface. But the main problem remains:

Where to place the interface? I read the original article on DIP, but I cannot agree with the interpretation that I should not put interfaces in the library. I have the feeling that this document is intended as a guideline on how to think about development (since "users define the interface, not the performers"). It's right? How do you use dependency inversion?

+8
c ++ dependencies deployment dip-principle modularity
source share
1 answer

Software can be considered as a combination of different layers:

  • One level - implementation level (roughly speaking, function level)

  • Another is the way data structures interact (the class level that DIP should apply first)

  • And another way to interact components (package layer). We would also like to use some kind of DIP here, if possible. Robert K. Martin insists that this layer is mainly business dependent (whatever that may be), and therefore the principles are slightly different: the principle of stable dependencies and the principle of stable abstractions (see The principle and practice of the Martin principle)

Now what should be emphasized in the principles of software development is that you should only apply them when you need to solve the problem that they are solving. If you have no problem, do not use them.

At the class level, you should use DIP if you have good reason to believe that your logging mechanism will be implemented by several classes. If you think that at the moment there will be only one logging mechanic, then using DIP is not great, because problems can not be solved.

Now the same choice should be made at the package level. However, packaging guidance is a deployment. Here:

 class ILogger { virtual void log(const std::string& s) = 0; }; class A : public ILogger { โ€ฆ }; class A2 : public ILogger { โ€ฆ }; 
  • If you think (for commercial reasons) that it makes sense to release A without A2, then create 4 libraries: one for ILogger, one for custom class B, one for A, one for A2.
  • If for some reason A and A2 need to be released together, then create only one library for ILogger, A and A2. If later they should be released separately, then break your library, but not now, because you remember: YAGNI .
  • If you have only one dependency on ILogger, then it also makes sense to create only one library with everything.
  • Do not release lib with ILogger and B, and the other with A, because then you have no advantages over solution 3, it is more complicated and, in addition, may violate another principle for packages: the principle of Acyclic-Dependencies.

In any case, this decision is mainly business dependent. Also remember that packaging should be done from the bottom up: create only a new package if you have many classes that you want to organize. Until you have this many classes, do not try to make early decisions, because you are almost certainly mistaken.

+2
source share

All Articles