Even in modern languages such as Java and C#, mutual compile-time
dependencies between two classes can cause build problems when they
cross package, namespace or component boundaries. For more traditional
languages like C++, such dependencies frequently cause severe
problems even within a namespace or component. While not the only
solution to this particular problem, Java-style interfaces provide a
means of reversing the direction of compile-time dependencies. For
languages that do not have a Java-style interface construct (C++,
Eiffel, etc), abstract classes substitute.
In object-oriented software, it is not unusual for designs to require objects of two classes to have a reference to each other. This is especially true of significant line of business applications. Often, these pairs of references represent two concepts in the software's problem domain that are naturally related to each other. For example, a particular person is related to an order they place on a web site. The person is the customer for order, and the order represents the purchase that the person is making.
Therefore, in a simple, order processing system for this web site,
it would not be unreasonable to expect to have a Customer class and an
Order class, each defining a reference to the other. Presumably, a
customer could place multiple orders over time and, therefore, the
Customer class requires a reference to some kind of set or list of
orders. Normally, this is not a problem because modern compilers handle
such mutual references easily. Even if the two classes are in different
packages, namespaces or components like those shown in figures 1 and 2,
the compiler can handle it as long as we compile both packages
together. However, if we need to compile the packages separately we
have a problem; we cannot compile either of the packages before the
other. In large systems with numerous such examples, this can become a
significant build problem.
package OrderManagement; |
package CustomerManagement; |
Figure
1: Mutual references between two Java classes in different packages
The Unified Modeling
Language (UML) calls these types
of relationship between objects of two classes, associations.
Associations are drawn as a solid line linking classes they relate.
Figure 2 shows how the code in figure 1 looks in UML; the asterisk
showing that multiple order objects may be related to the same customer
object.

Figure 2: Example of an association
spanning two package in UML
Various solutions exist to reduce two mutual dependencies to a single dependency. The simplest approach is to ignore the dependency in one of the directions:
When presented with a bi-directional association, often it is possible to ignore one direction because features do not exist that require navigating between objects in that direction. For example, if no feature exists that requires the Order objects to call operations or access properties of the related Customer object, we can remove the reference from the Order class to the Customer class. Problem solved! Now we can compile the OrderManagement package/namespace on its own and then compile the CustomerManagement package/namespace.
other times the application's features demand traversal of the association in both directions and this approach fails.System software, real-time applications and simple applications are often stable enough for such decisions to hold true. However, with today's rapidly changing requirements, design decisions like this can very quickly cause big problems for business applications developers.
Instead
of
implementing both directions of
an association using references, one direction can be derived each time
by searching the instances of the other class for one that points back
to the object we are holding.
Add a class-level or static method to one of the classes that loops through all the instances of that class selecting the subset that have a reference back to the specified object. Instead, for example, of accessing the related customer by asking an order object for its customer, we ask the Customer class for the customer object of a specified order object. Figure 3 shows the structure for this in code. Figure 4 does the same in UML.
package OrderManagement; |
package CustomerManagement; |
Figure
3: Resolve Bi-Directional Association via static lookup

Figure 4: UML showing look up operation for one direction of
association
UML Notation:
If it can be identified, the least frequently traversed direction of the association should be implemented this way. A SQL query run directly against a relational database used to persist these objects is one typical way of locating the right customer in this approach.
Java-style
interfaces
provide another possibility and the same can be achieved with C++-style
abstract classes. However, in my opinion, the interface concept makes
the picture clearer.
A mutual dependency
between two
classes can
be resolved by introducing an interface that is implemented by one of
the classes and referred to by the other. Figure 5 shows how a
bi-directional association becomes two dependencies in the same
direction when an interface is introduced in this way.


Now, instead of Class A having a
reference to Class B, it has a reference to the new Interface A. Class
B implements that interface (denoted by a dashed line with the hollow
arrowhead). The direction of the links between the two packages now
both go from Package B to Package A. Package A can be compiled without
any knowledge of Package B.
The
interface need only declare the subset of the public operations that
Class A needs to call on Class B.
Associations are not the
only relationships that produce mutual
compile-time dependencies between package, namespace or component. As
shown in strategy #2, the type of a parameter of an operation can
introduce a dependency to another package, namespace or component. In
addition, a method that creates an instance of a class in another
package as shown in figure 6 can introduce a dependency to that other package, and so
on.

Figure 6: Dependency from
instantiating objects of another class
In figure 6: Class A1
has a reference to Class B that it uses to create objects of Class A2.
Class A1 keeps a collection of these created objects.
UML Notation:
the solid diamond on the association between Class A1 and Class A2
indicates that this is a composition or strict whole-part relationship.
Objects of Class A2 are considered parts
of the concept modeled by objects of Class A1. For example,
The same technique of
introducing works in these scenarios too, and one common place where
this technique is often useful is across the boundary between the
problem domain (PD) and system interface (SI) logical layers.
Both business and industrial applications often need to communicate
with other systems. In these cases, it is generally good practice for
objects of problem domain or business classes to delegate to objects of
system interface classes the task of communicating with these systems.
In other words, a PD class invokes an operation of an SI class to make
use of a service provided by an external system. In turn, external
systems may invoke services provided by SI classes that then query or
update any relevant PD classes.
This two-way communication between our software and external systems
obviously introduces mutual dependencies between PD and SI classes.
Therefore, we introduce interfaces to change the direction of any
compile-time dependencies from PD to SI so that all dependencies
between the two layers go from SI to PD.
Figure 7 shows an example where a User PD class needs to call an
external system to retrieve user permission information.

Figure 7: System interface example
In figure 7, an ISecurity interface has been created in the PD
package (namespace, component, layer, etc), and objects of the User
class are given an appropriate implementation of this interface to work
with. The SI package defines a class, SecurityScheme, that implements
the PD ISecurity interface creating PD permission objects from security
information retrieveed from an external system (not shown).
Figure 7 clearly shows
the direction of the compile-time dependencies is from SI to PD. This
means that should the external security system ever change, as long as
the ISecurity interface can still be satisfied, only classes in the SI
layer need modifying to use the new security system. In addition, with
the interfaces in place, a separate team of
developers can work independently on the SI classes.