Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which effects does the Dependency Inversion Principle have to a project structure?

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?

like image 863
Stefan Weiser Avatar asked Aug 27 '14 04:08

Stefan Weiser


People also ask

What is the purpose of 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.

What is the benefit of dependency inversion?

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.

How Dependency Inversion principle is used in agile design methodology?

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.


1 Answers

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 {
    …
};
  1. If you believe (for business reasons) that it makes sense to release A without A2, then make 4 libraries : one for ILogger, one for the user-class B, one for A, one for A2.
  2. If for some reasons A and A2 be should be released together, then make only one library for ILogger, A and A2. If later on they should be released separatly, then break your library but not now because remember : YAGNI.
  3. If you have only one dependency to ILogger, then it could also make sense to create only one library with everything.
  4. Do not release a lib with ILogger and B, and another one with A because then you have no advantage compared to solution 3, this is more complex and could furthermore violate another principle for packages: the Acyclic-Dependencies Principle.

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.

like image 145
Bérenger Avatar answered Nov 03 '22 00:11

Bérenger