...
- Testing and maintainability: It is preferable to make a change somewhere (or patch) and limit the repercussions to as few packages as possible (ideally just one) as opposed to having to monitor or refine numerous packages. Inability to perform adequate testing because of cyclic dependencies is a frequent source of security vulnerabilities.
- Reusability: When a new version of a package is released, clients who reuse it do not have to test their existing code bases for compatibility with other packages that this particular package depends on. Sometimes, the reusable package evolves only to accommodate the changes to packages that it depends on.
- Releases and builds: Avoiding cycles also helps to steer the development towards an environment that fosters modularization. Owners of different packages are also redeemed from relying on other bleeding-edge packages.
- Deployment: By resolving the cycles, deployment is simplified, as runtime errors like the infamous
ClassNotFoundError, are reduced to a minimum by virtue of the toned down coupling between packages.
...
This noncompliant code example features two different packages named Account and User that consist of the classes AccountHolderClass AccountHolder and UserClass UserDetails, respectively. The class UserClass UserDetails extends from AccountHolderClass AccountHolder because a user is a kind of account holder. The class AccountHolderClass AccountHolder depends on a few non-static utility methods defined in UserClass UserDetails and must declare and use its instance. Likewise, the UserClass UserDetails depends on AccountHolderClass AccountHolder but instead chooses to extend from it. This vicious circle is one recipe for a cyclic dependency.
| Code Block | ||
|---|---|---|
| ||
package Account; import User.*; public class AccountHolderClassAccountHolder { private UserClassUserDetails ucud; // Uses a class defined in package User protectedfinal synchronized void depositFunds(String username, double amount) { // Use a utility method of UserClassUserDetails to check if username exists if(ucud.exists(username)) { // Deposit the amount } } protected final double getBalance(String accountNumber) { // return the account balance return 1.0; } } package User; import Account.*; class UserClassUserDetails extends AccountHolderClassAccountHolder { protectedpublic synchronized double getUserBalance(String accountNumber) { // Use a method of AccountHolderClassAccountHolder to get the account balance return getBalance(accountNumber); } public boolean exists(String username) { // Check whether user exists return true; // Exists } } |
Compliant Solution
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 dependency is eliminated by ensuring that the AccountHolderClass AccountHolder does not use an instance of UserClass UserDetails, but instead relies on the interface by importing the Bank package (not 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 gives the AccountHolderClass AccountHolder a solid contract to bank upon. Additionally, UserClass UserDetails implements the interface and provides concrete implementations of the methods while at the same time, inheriting the other methods from AccountHolderClass.
It might appear that the interface BankApplication contains 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' methods polymorphically (such as calling ba.getBalance(), with an overridden implementation of the method in UserClass).
AccountHolder.
| Code Block | ||
|---|---|---|
| ||
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.*; // Import from a third package
class AccountHolderClassAccountHolder {
publicfinal synchronized void depositFunds(BankApplication ba, String username, double amount) {
// Use a utility method of UserClassUserDetails to check if username exists
if(ba.exists(username)) {
// Deposit the amount
}
}
public final double getBalance(String accountNumber) {
// Return the account balance
return 1.0;
}
}
package User;
import Account.*; // One way dependency
import Bank.*; // Import from a third package
class UserClassUserDetails extends AccountHolderClassAccountHolder implements BankApplication {
public synchronized double getUserBalance(String accountNumber) {
// Use a method of AccountHolderClassAccountHolder to get the account balance
return getBalance(accountNumber);
}
public boolean exists(String username) {
// Check if user exists
return true;
}
}
package Implementer;
import Bank.*;
import Account.*;
import User.*;
class BankOperations {
private BankApplication ba;
public BankOperations(BankApplication ba) {
this.ba = ba;
}
public void doUserActions() {
System.out.println(ba.exists("user"));
System.out.println(ba.getUserBalance("1111"));
}
public static void main(String[] args) {
AccountHolderClassAccountHolder ac = new AccountHolderClassAccountHolder();
ac.depositFunds(new UserClassUserDetails(), "user", 1.0); // Pass an interface argument
BankOperations bo = new BankOperations(new UserClassUserDetails());
bo.doUserActions();
}
}
|
It might appear that the interface BankApplication contains 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' methods polymorphically (such as calling ba.getBalance(), with an overridden implementation of the method in UserDetails).
Risk Assessment
Cyclic dependencies between packages can lead to fragile builds. A security vulnerability in a package can easily percolate to several other packages.
...