Cyclic dependencies among Classes are a common issue in software development. They are best resolved at design level. This article explains how to do this. It also provides some intuitive insights into Interfaces and dependencies.
Yesterday I wrote about dependencies and how they are displayed in UML. Today I build upon that basic knowledge and show you how to resolve cyclic dependencies among your classes.
Code vs. Design level
I often see programmers who try to solve this problem by discussing about source code. But this is clearly a design problem and is much easier to solve on design level.
One reason is that code (i.e. text) is inherently sequential, while graphical notations are 2-dimensional. Cycles are easy to display in 2D-space, but it’s impossible to display them in a sequence (i.e. 1D-space).
Let’s take a simple example. We have a restaurant which needs to know its guests and people who need to know the restaurant they are visiting this evening. We model this as two classes, Restaurant and Person, which store each other in a field:
Our cyclic dependency is visible better, when using arrows to visualize the associations:
How to resolve the cycle?
Introducing an Interface
We can resolve the cycle by using a simple trick called Inversion of Dependency.
First, we need to decide in which direction the dependency should preferably point. Let’s say the Restaurant wants to know everything about the Person, because storing abnormal amounts of personal data is trendy. But the Person doesn’t need to know all details about the Restaurant, it just needs an address.
Secondly we just put a new interface between the two classes:
Now the Person doesn’t depend on the Restaurant anymore. Instead both classes depend on the new Interface. The “dependency flow” isn’t cyclic anymore.
A second Interface
You could break the dependencies further, by introducing another interface, but this is not necessary in most cases. I’ll show it here anyway, just so you have seen it once:
All arrows point to the interfaces between the classes now.
Although this step was not necessary, the result can help you get a feeling for interfaces. This example shows three things one needs to develop an intuition for:
- Interfaces are sinks for the dependency flow. Often, indirect dependencies end there.
- Inserting an Interface breaks the dependency flow by splitting a dependency in two and inverting one dependency.
- Interfaces decouple classes. Classes separated by interfaces only depend on what is communicated, not on how or by whom.
This trick works the same way for cycles including more classes. For example, you could resolve the following cycle
by introducing an a new interface:
Initializing the Interface
Our Person from the previous example only requires a Location now. But still the Location needs to be instantiated with an object of a concrete class (i.e. Restaurant). If the Person would construct the Restaurant object itself, just to save it in a Location variable, the dependency to Restaurant would be revived.
Instead, you could either use dependency injection or some creational design pattern, like the abstract factory pattern. Neither of those will be covered in this article.
More complex examples
The above example was overly simplified. In reality, your interfaces often are not total sinks, but have some dependencies themselves. For example, the getAddress() method of Location may return an Address class. This means that the Location requires the existence of an Address class and is thus dependent on Address.
Also interfaces can inherit from other interfaces:
It may seem like I was misleading you when I told you that Interfaces act like dependency sinks. But it’s still partially true. In fact, the remaining dependencies are very “weak” forms of dependencies.
The inheritance only passes the dependency to another interface, which is an “almost” sink itself.
The dependency to another class only required the simple existence of that class. You could modify Address however you like. As long as it remains an Address, the Interface is not affected.
If even the simple class existence dependency is too much, you can invert the bothersome dependency like we did before:
Now you reduced the dependency on the existence of a class to a dependency on the existence of an interface. Thus, the sink only depends on another sink.
You now know how to resolve cyclic dependencies on a design level. You also have got some intuitive understanding of what interfaces are and how to use them. It was also mentioned that different kinds of dependencies exist and why some are weaker than others.