Both The Elements of Java Style [Vermeulen 2000] and the JPL Java Coding Standard [Havelund 2010] require that the dependency structure of a package must never contain cycles; that is, it must be representable as a directed acyclic graph (DAG).
Eliminating cycles between packages has several advantages:
- Testing and maintainability. Cyclic dependencies magnify the repercussions of changes or patches to source code. Reducing the repercussions of changes simplifies testing and improves maintainability. Inability to perform adequate testing because of cyclic dependencies is a frequent source of security vulnerabilities.
- Reusability. Cyclic dependencies between packages require that the packages be released and upgraded in lockstep. This requirement reduces reusability.
- Releases and builds. Avoiding cycles also helps to steer the development toward an environment that fosters modularization.
- Deployment. Avoiding cyclic dependencies between packages reduces coupling between packages. Reduced coupling reduces the frequency of runtime errors such as
ClassNotFoundError. This, in turn, simplifies deployment.
Noncompliant Code Example
This noncompliant code example contains packages named
user that consist of the classes
UserDetails respectively. The class
UserDetails extends from
AccountHolder because a user is a kind of account holder. The class
AccountHolder depends on a nonstatic utility method defined in the
User class. Likewise, the
UserDetails depends on
AccountHolder by extending it.
The tight coupling between the classes in the two packages can be weakened by introducing an interface called
BankApplication in a third package,
bank. The cyclic package dependency is eliminated by ensuring that the
AccountHolder does not depend on
User but instead relies on the interface by importing the
bank package (and not by implementing the interface).
In this compliant solution, such functionality is achieved by adding a parameter of the interface type
BankApplication to the
depositFunds() method. This solution gives the
AccountHolder a solid contract to bank on. Additionally,
UserDetails implements the interface and provides concrete implementations of the methods while at the same time inheriting the other methods from
BankApplication appears to contain superfluous methods such as
getBalance(). These methods are present so that if the subclass overrides them, the superclass retains the capability of internally invoking the subclass's methods polymorphically (for example, calling
ba.getBalance() with an overridden implementation of the method in
UserDetails). One consequence of this solution is that methods declared in the interface are required to be public in the classes that define them.
Cyclic dependencies between packages can result in fragile builds. A security vulnerability in a package can easily percolate to other packages.
|[Havelund 2010]||JPL Coding Standard, Version 1.1|
§1.2.5, "Acyclic Dependencies Principle"
Chapter 1, "OO Principles and Patterns"
|[Vermeulen 2000]||The Elements of Java Style|