Acyclic-Dependencies Principle (ADP)

Acyclic-Dependencies Principle (ADP)

Acyclic-Dependencies Principle (ADP)

written By : Sridhar Dhayalan <sridhar.d@mitosistech.com>

Description:

Dependency graph of packages or components should have no cycles.

Why ADP needed?

  • ADP ensures no cycles in the package or components
  • When two or more packages are involved in a dependency cycle, it becomes very difficult to stabilize the application.
  • When two or more packages are involved in a dependency cycle, Developer or Tester will not able to conduct independent module unit Test.
  • ADP ensures package level High cohesion

 

How to Achieve No Cycles  in packages or components

With the help of other design principles this can be achieved.

  • Component / Package Cycle removal using DIP and ISP principle

If the cycle is identified in the package of component, using DIP and ISP design principle, the package of component to be segregated with new class or package.

Breaking a cycle – New package: Break out of dependency target – Apply dependency inversion (DIP) + interface segregation (ISP)

  • Component cycle removal using Common-Reuse Principle (CRP)

The CRP states that classes that tend to be reused together belong in the same package together. By this we will ensure no cycle.

  • The Common-Closure Principle ( CCP)

The classes in a package should be closed together against the same kind of changes. A change that affects a closed package affects all the classes in that package and no other packages.

The classes that are interdependent will generally want to be re-used together and will have to change together. If you split those classes into different packages using CRP and CCP principle it is compliance of ADP as well

ADP, ACP and CCP principles drive you to either group the interdependent classes into the same package or to break the inter-dependencies.

Bad Example :

package Account;
import User.*;
public class AccountHolder {
private UserDetails ud;  // Uses a class defined in package User

synchronized void depositFunds(String username, double amount) {
// Use a utility method of UserDetails to check whether username exists
if (ud.exists(username)) {
// Deposit the amount
}
}

protected double getBalance(String accountNumber) {
// return the account balance
return 1.0;
}
}
}

package User;
import Account.*;
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 boolean exists(String username) {
// Check whether user exists
return true; // Exists
}
}


In the above code:

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 AccountHolder does not use an instance of UserDetails, 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 gives the AccountHolder a solid contract to bank upon. Additionally, UserDetails implements the interface and provides concrete implementations of the methods while at the same time, inheriting the other methods from AccountHolder.

 

Good example:

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 AccountHolder { 
 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.*; // One way dependency
import Bank.*; // 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;
 }
}
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) {
 AccountHolder ac = new AccountHolder();
 ac.depositFunds(new UserDetails(), "user", 1.0); // Pass an interface argument
 BankOperations bo = new BankOperations(new UserDetails());
 bo.doUserActions();
 }
}

 

 

Conclusion:

The interface BankApplicationappears 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’ methods polymorphically (for example calling ba.getBalance(), with an overridden implementation of the method in UserDetails).

One consequence of using this implementation is that methods declared in the interface are required to be public in the classes that define them.

 written By : Sridhar Dhayalan <sridhar.d@mitosistech.com>


Recommended Posts

Comments

  1. Packages and components plays major roles in modularity. ADP , CCP and CRP ensures modularity and high cohesion in packages level

Comments are closed.