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:
ClassNotFoundError
. This, in turn, simplifies deployment.This noncompliant code example contains packages named account
and user
that consist of the classes AccountHolder
, User
, and 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.
package account; import user.User; public class AccountHolder { private User user; public void setUser(User newUser) {user = newUser;} synchronized void depositFunds(String username, double amount) { // Use a utility method of User to check whether username exists if (user.exists(username)) { // Deposit the amount } } protected double getBalance(String accountNumber) { // Return the account balance return 1.0; } } package user; import account.AccountHolder; public class UserDetails extends AccountHolder { public synchronized double getUserBalance(String accountNumber) { // Use a method of AccountHolder to get the account balance return getBalance(accountNumber); } } public class User { public boolean exists(String username) { // Check whether user exists return true; // Exists } } |
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 AccountHolder
.
package bank; public interface BankApplication { void depositFunds(BankApplication ba, String username, double amount); double getBalance(String accountNumber); double getUserBalance(String accountNumber); boolean exists(String username); } package account; import bank.BankApplication; // Import from a third package class AccountHolder { private BankApplication ba; public void setBankApplication(BankApplication newBA) { ba = newBA; } public synchronized void depositFunds(BankApplication ba, String username, double amount) { // Use a utility method of UserDetails // to check whether username exists if (ba.exists(username)) { // Deposit the amount } } public double getBalance(String accountNumber) { // Return the account balance return 1.0; } } package user; import account.AccountHolder; // One-way dependency import bank.BankApplication; // Import from a third package public class UserDetails extends AccountHolder implements BankApplication { public synchronized double getUserBalance( String accountNumber) { // Use a method of AccountHolder to get the account balance return getBalance(accountNumber); } public boolean exists(String username) { // Check whether user exists return true; } } |
The interface BankApplication
appears to contain superfluous methods such as depositFunds()
and 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.
Tool | Version | Checker | Description |
---|---|---|---|
Parasoft Jtest | CERT.DCL60.ACD | Ensure that files do not contain cyclical dependencies |
[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 |