4

Background

Time ago I learnt something about package design, in particular about loose coupling:

The Acyclic Dependencies Principle

The dependency structure between packages must be a Directed Acyclic Graph (DAG). That is, there must be no cycles in the dependency structure.

Robert Cecil Martin aka uncle Bob, Granularity

The questions

  1. Why are there systems like nodejs (where I work the most, but question may apply to others) supporting cyclic dependencies between modules? To clarify: Within nodejs, a module can be either one source code file or one folder containing a package.

  2. Are there any real-life use cases considered to be valid? Please, not being "examples of bad design" (explained below)

Examples of bad design

Question (2) refers to examples where there are cyclic dependencies which would be better refactored to avoid them, resulting on a better design.

As an example of what I mean with the preceding assertion, see this answer by Doc Brown where he exposes the "file system example", but please note this question is not about cyclic dependencies between Classes.


Related Questions at programmers SE

  • 1
    Packages != Classes. While dependency rules are common between them, there are some differences. – Euphoric Apr 30 '16 at 17:08
  • I'm having trouble figuring out how this question is different from the other ones you've linked; it sounds like at least part of your question is simply repeating the old one "but please give a better answer than Doc Brown did". Aside from pointing out the distinction between cyclic dependencies at class level versus those at package level, I'm not really sure what there is to add. – Ixrec Apr 30 '16 at 17:36
  • 1
  • 4
    I'm not sure this is what you're about to ask but from what I have seen, most language / framework / library designers, while certainly not encouraging bad practices, still tend to opt for generality rather than putting arbitrary restrictions into place. If you want to be stupid, the tool shouldn't stop you… – 5gon12eder Apr 30 '16 at 21:03
  • 1
    Tools that support good design are nice. Tools that prevent getting things done in time are not. Every software system of any business value has some design flaws. Not all flaws are results of stupidity or lack of skill. Some might even be a result of a well thought out compromise. – COME FROM May 3 '16 at 8:30
2

Cyclic module dependencies frequently crop up when you have a module that serves as an interface between user code and (several) implementation modules.

Typically, such an interface module defines types and generic functionality for the implementation modules to use, which is why the implementation modules depend on the interface module. However, to provide a true abstraction to the user code (= avoid user code dependencies on the implementation modules), the interface module has to interact with the different implementation modules, and thus depend on them.

If you replace "module" with "class", this becomes a lot clearer: The interface module is an abstract class that provides the entire user interface. The different implementations inherit from the abstract class and thus depend on the abstract interface. However, to make the very existence of the subclasses invisible to the user, the abstract class also has to provide factory methods that select the different implementations under the hood. Since the factories need to instanciate the concrete classes, they depend on them.

Of course, such a class architecture would usually be encapsulated within a single module, so you don't get circular module dependencies yet. However, the same structure can apply to code in a larger context, leading to a situation where you would have true cyclic module dependencies.

Usually, such cyclic dependencies can be broken up by splitting the abstract interface into two parts, one on which the implementation modules depend, and one that depends on the implementation modules. This usually has the downsides of 1. one module being too small to justify it being a module, and 2. adding complexity to the public interface since user code now has to depend on two modules directly.

Another approach to break the cyclic dependencies would be for the implementation modules to register themselves with the interface module. This too has two downsides: 1. It forces a single callback interface into the implementation modules, which may not be appropriate, and 2. it significantly adds complexity for the registering and the handling of the registered implementations within the interface module.

So, yes, there are valid situations where restriction to non-circular module dependencies leads to more complex code, making the use of a dependency cycle the preferable alternative. And good programming languages allow for this.

  • nice answer, and sorry for comming that late – laconbass Mar 7 at 1:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.