- The dependency structure between packages must not contain cyclic dependencies. (http://www.objectmentor.com/resources/articles/granularity.pdf)
when applied to classes.
One of the PrinciplesOfObjectOrientedDesign
Hmm. Packages? What about classes?
Then again, you can get silly ...
I'm modeling a network. Nodes and links. Sure, I can abstract base classes from nodes and links so neither has direct knowledge of the other ... but this is without benefit. 1:1 cyclic dependencies are both natural and harmless.
Longer cycles than 2, however, are indeed the source of great confusion. There's a fair case to put, I think, that a class is
a cyclic dependency; certainly whenever I've found a >2ary cycle in a design, I've found a class ... -- PeterMerel
Different principle: Reduce cyclic dependencies as much as reasonable. I've found observers are often useful in doing this.
By the way, Lakos uses exactly this example, a directed graph with Nodes and Edges, showing how to factor out the physical cycle between the two classes. He takes it to quite some length: the first step, introducing a meta-class Graph that allows the physical cycle to be converted to a "in name only" cycle (pointers and references), this first step is reasonable, but by the time he completely refactors and introduces 3 more helper classes it looks silly. It's something like normalization in databases: you can do it completely, but few people ever do.
The database analogy is appropriate. "Graph" is really a relation between "Nodes" and "Edges"; the fact that a Node contains a list of Edges, and vice versa, is just an artifact of the implementation. Object oriented programming languages have improved support for objects, but have not much improved support for relations; object oriented and object relational database languages go further. There has been some work on implementing relations as a programming tool in object oriented languages. See, for example, Jiri Soukup: Data Structures as Objects, Dr Dobbs J., Oct. 1999. http://www.codefarms.com/
Anyway... if the relation is a separate object, then you eliminate the cyclic dependencies, physical and/or logical. Then pointers directly from Node to Edge, or vice versa, are just a performance optimization.
There are almost always cyclic dependencies between base (parent) and derived (child) classes when some functionality is in one and some in the other (e.g., where the base class has a pure abstract function).
Perhaps we mean different things by the word dependency. What I mean is that a change to one may require a change to the other. So although it is possible to introduce implementation dependencies from a base class onto a derived class, I'd say that such dependencies are good refactoring candidates. Derived must depend on base, but the other way round, if appropriate, should be made explicit for maintainability's sake.
What Lakos is talking about is more like libraries.
Second known use: Shlaer and Mellor say the same thing for architectural layers (or whatever they call them).
Third known use: UnitTest
s depend on being able to verify some layer of software, then verify higher layers assuming the lower layers work. -- PaulChisholm
If the function calls another (non-stubbed) function, then it's not a unit test. To do a proper unit test, you must stub the functions it calls, so that you can take control of all inputs to the function. -- BenPope?
Oh, certainly agreed with regard to packages - all I'm saying is that it often also pays to apply this criterion to classes.
I ran head-on into this principle as part of a team developing a Windows App. Our maximal cycle consisted of 12 DLLs. In order to unit test any one of the 12, you had to load all 12. The cycles were not designed in, rather they crept in as a result of defect fixing during system integration. Eventually, the cycles were broken by ReFactoring
, use of DesignPatterns
, and paying attention to the LawOfDemeter
. -- KentSchnaith
This page is quite terse. At risk of being florid, and please refactor this away if it is redundant, the general principle here is that dependent entities should not be separated. If they are separated then they could be updated independently leading to breakage. This is the usual problem of coupling without cohesion. -- RichardHenderson
Use a tool to get the cyclic dependencies. In Java I'm testing Free tools: OptimalAdvisor?
) and JDepend (http://www.clarkware.com/software/JDepend.html