1

I am currently reading an otherwise excellent tutorial regarding the Dependency Inversion Principle

https://www.baeldung.com/java-dependency-inversion-principle

and there is something I am not able to interpret despite considerable long time of thinking

The relevant part from definition of DIP: "High-level modules should not depend on low-level modules. Both should depend on abstractions."

In point 3.1 "Design Choices and the DIP" the author introduces the principle through an example where a StringProcessor class uses a StringReader and a StringWriter component and gives multiple design choices wrt using interfaces/classes and packages. My problem is with choice 2 which is

"StringReader and StringWriter are interfaces placed in the same package along with the implementations. StringProcessor now depends on abstractions, but the low-level components don't. We have not achieved inversion of dependencies yet"

StringProcessor is the "high level component" which depends on the "abstractions" i.e. StringReader and StringWriter interfaces, thereby fulfilling DIP definition from one side, that is clear. Now given the terminology used throughout the article "the implementations" referred in the first sentence, e.g. a ConcreteStringReader and a ConcreteStringWriter class would be the "low level components" here and what I am just not able to understand how they do not depend on the "abstractions" i.e. on the interfaces they implement, regardless of the containing packages

Obviously putting implementations in the same package with their interfaces might not be the best from code organization point of view, but how this violates the verbatim DIP definition above wrt depending on abstractions is currently beyond my understanding

Perhaps someone with a deeper theoretical knowledge on the topic might help me out here

hammerfest
  • 2,203
  • 1
  • 20
  • 43
  • 4
    "Perhaps someone with a deeper theoretical knowledge..." I think this is a commonly held misunderstanding about SOLID. There *is* no deep understanding to be unlocked. The whole thing is a load of crap. – Michael Jan 02 '21 at 16:21
  • 1
    I disagree, sort of. Each SOLID principle is good, some of them are essential to understand and apply very broadly. They are necessary, but I don't think they are sufficient, and they aren't magic. Like design patterns, they are more of a handy way to refer to something good developers should have learned through experience, but not a good way to introduce and instill those ideas to new developers. – erickson Jan 02 '21 at 18:38
  • @erickson Yeah, very well said. I think we broadly agree, except for one thing: "each SOLID principle is good". IMO OCP is an actively harmful, incoherent mess of an idea. The rest are okay at their core, but they're obtusely constructed. – Michael Jan 03 '21 at 13:17

2 Answers2

2

The general concept implied is that one package equates to one level of abstraction. Therefore, in section 3.1.2, the concrete implementations "own" their abstractions by virtue of being in the same package; because anywhere the package is published, those implementations are along for the ride. The coupling among classes that share a package manifests to some extent in syntax, even in Java 8. For example, import statements aren't necessary and classes & methods with the default access modifier are visible.

Still, it's easier to see the flaw in section 3.1.2 in light of JPMS features. Modules are defined at the package level, formalizing the concept that a package is a single level of abstraction. In terms of the DIP then, dependencies are also considered at the package level. If a package contains concrete implementations, it is considered low level, and should not have incoming dependencies.

An entire book that dives deep into this topic is Java Application Architecture: Modularity Patterns.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
  • Thank you. The classic package example seems to explain that the implementations "own" and not "depend on" their abstractions thereby answering my question as it is. However the JPMS example confused me somewhat because I tend to understand that the DIP violation here is actually that the module containing the high level component is depending on the other module containing together the abstractions and the low level components thereby becoming a low level module - contradicting the author who states "(high level component) now depends on abstractions". Did I get you right? – hammerfest Jan 03 '21 at 16:54
  • 1
    TBH, I'm not sure what the author means in 3.1.2 by, "_...the low-level components don't._" I don't think it makes a difference to the low-level components themselves whether their abstraction lives in the same package; but it makes a big difference to other consumers of the abstraction, especially high-level components. I think of low-level components as polluting or infecting their package (with implementation details). To avoid spreading the infection (through transitive dependencies) the DIP prohibits depending on anything in a low-level package. – jaco0646 Jan 03 '21 at 17:42
  • The JPMS doesn't change the DIP. It only makes high-level vs low-level package boundaries explicit, so that DIP violations will show up in `module-info.java` files. – jaco0646 Jan 03 '21 at 17:49
  • I see. In light of that, the mentioned other statement in 3.1.2 _"(high-level component) now depends on abstractions"_ does not seem to be valid either; given that it is actually depending on the infected package/module containing implementation details? – hammerfest Jan 03 '21 at 19:58
  • 1
    The "infected" package contains both abstractions and implementation details, so you could say the high-level component depends on both; but it's the implementation details that specifically violate the DIP, because everything in a package is coupled. – jaco0646 Jan 03 '21 at 22:07
1

"StringReader and StringWriter are interfaces placed in the same package along with the implementations. StringProcessor now depends on abstractions, but the low-level components don't. We have not achieved inversion of dependencies yet"

While it's indeed not DIP, the explanation is wrong IMO. The problem is that the author failed to recognize the distinction between a class/component and a layer/package/module. DIP is a principle that's only applicable to relationships between modules, which obviously is not applicable when there's a single module.

As for the point #4,

Likewise, item 4 is a more decoupled DIP implementation. In this variant of the pattern, neither the high-level component nor the low-level ones have the ownership of the abstractions.

We need to be very careful here because "ownership" goes beyond "being in the same package".

e.g. this would be a DIP violation

enter image description here

plalx
  • 42,889
  • 6
  • 74
  • 90
  • In the picture, I think JPMS (or OSGi) can define an explicit boundary between the `spi` and `impl` packages, and this is why packages (rather than jars) are considered the basic unit of modularity (and therefore the unit of dependency inversion). It is possible to publish a jar containing both but exposing only one. – jaco0646 Jan 04 '21 at 15:02
  • 1
    @jaco0646 Yes, but what I want to focus on here is that `my_project` doesn't control the `spi` and so is dependent on changes that are possibly driven by the low-level library design decisions. This conceptually violates DIP too IMO. I think the interface needs to be either defined in the high-level module, in a package owned by the implementor (project/company) of the high-level module OR owned or deployed as a global standard. – plalx Jan 04 '21 at 15:20