Acyclic Relationships
by kirk knoernschild
Statement
Module relationships should be uni-directional.
Description
When you define a relationship between two system components, their coupling is increased. Some degree of coupling is a necessary simply because components need to work together with each other to accomplish some task. But certain types of coupling should be avoided. The diagram at right in Figure 1 illustrates a bi-directional relationship between two components. That is, each components has a dependency on the other, and the accompanying code to the right of this component diagram shows the import statement in the classes within each of the respective components. A more desirableapproach is the diagram at left in Figure 1 where a uni-directional relationship exists between the two components.
To determine if a bi-directional relationship exists between any set of components, you can always apply the following rule:
If beginning with a component A, you can follow the dependency relationships between the set of components that A is directly or indirectly dependent upon, and you find any dependency on component A within that set, then a bi-directional dependency exists between your component structure.
While the diagram in Figure 1 illustrates an obvious bi-directional relationship, not all violations of this heuristic are as apparent. In more complex situations, indirect bi-directional relationships might exist between three or more components. Because most medium to large applications typically consist of dozens of components, it’s imperative that you are judicious when evaluating the component structure for bi-directional dependencies.

Figure 1: Cyclic and Acyclic Relationships
Implementation Variations
There are a few ways to resolve cycles in the dependency relationships among your components, regardless of whether you’re attempting to break a direct or indirect dependency cycle. Escalation is the process of moving the cause of the cyclic dependency to a managing component at a higher level. Demotion is the process of moving the cause of the cyclic dependency down to a lower level component. The third option is to use a Callback, where the component defines an abstraction that is injected into the dependent component, resulting in an implementation that resembles the Observer pattern. Using callbacks, it is possible to completely remove the dependency. Examples of each technique are illustrated in the sample code section.
Consequences
Of all module relationships, this is arguably the most important. I rarely allow any bi-directional dependencies to creep into an application because of the significant impact these dependencies have on maintainability and reusability. The coupling caused by a bi-directional dependency has the following implications:
- Whenever a dependency exists between two components, the impact of change is increased. If the two components have a bi-directional dependency, the ripple effect of change is dramatically increased. Changing one component may cause changes to all dependent components, which in turn can cause a rather dramatic ripple effect throughout, and potentiall across, applications.
- Any set of components with a direct or indirect bi-directional relationship will inevitably be less reusable. To use the contents of one components, you also require the behavior of all components it is dependent upon. As you saw in the example for Heuristic 7.1, this placed severe limitations on the ability to reuse the EmployeeDAO because of the very tight functional and data coupling between the two components.
Breaking the dependency cycles in your physical structure allows you to accommodate a number of other proven practices. You can levelize your components. Any cycles in the dependency structure prevents you from levelizing your application. Levelization allows you to perform a LevelizedBuild and enforce your component relationships. Levelization also facilitates testing by allowing you to create a TestComponent that can verify your components in isolation.
Sample Code
Recall from Chapter 5 the bi-directional relationship between our Customer and Bill classes, which were each allocated to their respective modules, a customer.jar and billing.jar, respectively. The initial structure is shown in the diagram in Figure 2. Note the bi-directional relationshp between the two classes, which causes a cyclic relationship between the two modules.

Figure 2: Cyclic Relationship Between Modules
The code for the Bill class can be seen in Listing 1. Note how the Bill class accepts a Customer instance in its constructor so that the pay method can use the Customer class to calculate the discount.
package com.kirkk.bill;
import com.kirkk.cust.*;
import java.math.BigDecimal;
public class Bill {
private BigDecimal chargeAmount;
private Customer customer;
public Bill(Customer customer, BigDecimal chargeAmount) {
this.customer = customer;
this.chargeAmount = chargeAmount;
}
public BigDecimal getChargeAmount() {
return this.chargeAmount;
}
public BigDecimal pay() {
BigDecimal discount = new BigDecimal(1).subtract(this.customer.getDiscountAmount()).setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal paidAmount = this.chargeAmount.multiply(discount).setScale(2);
//make the payment...
return paidAmount;
}
}
Listing 1: The Bill Class
Now, the code for the Customer class can be seen in Listing 2.Note that the Customer class has a list of Bill instances, and when the createBill method is called, a new Bill instance is created and added to the list. The getDiscountAmount method is called by Bill in Listing 1 to calculate the customer discount. Hence, the bi-directional relationship between Customer and Bill.
package com.kirkk.cust;
import java.util.*;
import java.math.BigDecimal;
import com.kirkk.bill.*;
public class Customer {
private List bills;
public BigDecimal getDiscountAmount() {
if (bills.size() > 5) {
return new BigDecimal(0.1);
} else {
return new BigDecimal(0.03);
}
}
public List getBills() {
return this.bills;
}
public void createBill(BigDecimal chargeAmount) {
Bill bill = new Bill(this, chargeAmount);
if (bills == null) {
bills = new ArrayList();
}
bills.add(bill);
}
}
Listing 2: The Customer Class
Our goal is to break the cyclic dependency between customer.jar and billing.jar, and we’re going to look at three different ways to do this.
Escalation
The first technique we’re going to apply is called Escalation. With Escalation, we break the cyclic dependencies by escalating the cause of the dependency to a higher level entity. Before we do that, we need to more fully understand why a cyclic dependency exists. This reason follows:
A Customer has a list of Bill instances. When the pay method on Bill is invoked, the Bill needs to determine if a discount should be applied. The discount is a product of the Customer the bill belongs to, not necessarily the Bill. Therefor, the Bill class calls a method on Customer to determine the appropriate discount amount. Think of it this way…The Customer represents a payee and we negotiate a discount with each Payee. The calculation of this discounted amount is encapsulated within the Customer.
So the cause of the dependency centers around the discount calculation and payment of a bill. To break this dependency, we want to escalate the cause of the dependency up to a higher level class – the CustomerMediator. The mediator now encapsulates calculation of the discount and passes that to the bill class. (The best way to see this change is to look at the modified PaymentTest class.) Once the cycle is broken, I can modify the build script to bundle the mediator into it’s own module. If you dig a bit more deeply into the class structure, you’ll wonder why I didn’t just pass the discount amount from Customer into Bill. Don’t worry about that. This example is slightly contrived because escalation isn’t the best way to solve this type of problem. The key takeaway is that we’ve escalated the dependency up to the mediator.jar bundle, breaking the cyclic dependency.

Figure 3: Breaking the Cycle using Escalation
The code for the PaymentMediator class can be seen in Listing 3. Note how we’ve moved the pay method from Bill and the calculateDiscount method from Customer up to the PaymentMediator. The PaymentMediator now coordinates the activities surrounding the payment, which results in the cyclic dependency being broken.
package com.kirkk.mediator;
import java.math.*;
import java.util.*;
import com.kirkk.cust.*;
import com.kirkk.bill.*;
public class PaymentMediator {
private Customer customer;
public PaymentMediator(Customer customer) {
this.customer = customer;
}
public BigDecimal pay(Bill bill) {
BigDecimal discount = new BigDecimal(1).subtract(this.getDiscountAmount()).setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal paidAmount = bill.getChargeAmount().multiply(discount).setScale(2);
//make the payment...
return paidAmount;
}
private BigDecimal getDiscountAmount() {
if (this.customer.getBills().size() > 5) {
return new BigDecimal(0.1);
} else {
return new BigDecimal(0.03);
}
}
}
Listing 3: The PaymentMediator Class
Demotion
Another way to resolve a cyclic dependency is to use demotion. With demotion, we push the cause of the dependency to a lower level module. Exactly the opposite of escalation. We do this by introducing a DicountCalculator class that will be passed into the Bill class. The Customer class will serve as the factory for the DiscountCalculator, since it’s the Customer that knows the discount that must be applied. The new structure can be seen in Figure 4.

Figure 4: Breaking the Cycle using Demotion
The code for the DiscountCalculator class can be seen in Listing 4. When the Customer class creates the DiscountCalculator, it will pass in the number of bills. When the pay method on Bill is called, it will invoke the getDiscountAmount method on the DiscountCalculator to determine the discount amount.
package com.kirkk.calc;
import java.math.*;
import java.util.*;
public class DiscountCalculator {
private Integer numBills;
public DiscountCalculator(Integer numBills) {
this.numBills = numBills;
}
public BigDecimal getDiscountAmount() {
if (numBills.intValue() > 5) {
return new BigDecimal(0.1);
} else {
return new BigDecimal(0.03);
}
}
}
Listing 4: The DiscountCalculator class
Already you can see how this is a more natural solution than escalation for this particular type of cyclic dependency problem. What’s the key difference you might ask? With escalation, notice how I would be able to deploy the cust.jar and bill.jar modules independently. While demotion is a more natural solution in this situation, it also means that to deploy bill.jar or cust.jar, I must also deploy calc.jar. The solution I chose is always going to be contextual and the ideal solution is likely to shift throughout the development lifecycle.
Callback
Using a Callback is similar to the Observer pattern. With this approach, we’ll refactor our DiscountCalculator class to an interface, and then modify the Customer class to implement this interface. This new class structure can be seen in Figure 5. In fact, we’ve seen this example already. In Section 5.2.2, we broke the cyclic dependency between Customer and Bill using a Callback.

Figure 5: Breaking the Cycle using a Callback
As it happens in this specific situation, using a Callback represents a combination of demotion and our initial solution. We’ll go back to passing the Customer into the Bill, but will pass it in as a DiscountCalculator type. Whereas in the Demotion example we bundled the DiscountCalculator in a separate module, we’ll now just include it in our billing.jar module. Note that putting the DiscountCalculator in the customer.jar module would introduce the cyclic dependency we’re trying to get rid of. The DiscountCalculator interface is quite simple, and is shown in Listing 5.
package com.kirkk.bill;
import java.math.BigDecimal;
public interface DiscountCalculator {
public BigDecimal getDiscountAmount();
}
Listing 5: The DiscountCalculator Interface
Wrapping Up
Cyclic relationships are the worst form of dependency between modules. The increased coupling that accompanies cyclic relationships hamper understanding, inhibit reuse, increase the overall complexity of the software system. If modules have cyclic relationships, they essentially function as a single module. Either the relationships should be broken, or the development team might as well combine them into a single module. If there is an absolute surrounding module design, it is that cyclic relationships between modules should be avoided at all costs.