November 17, 2004Circular dependencies are not always evilMike Spille's article on Inversion of Control containers was, as always, thoroughly researched and quite illuminating. It also brought up the old issue of cyclical dependencies, which are usually identified as a very ugly practice. Software fundamentalists are quick to point out that if you see a cycle in your classes or your packages, you should immediately remove it. I wish it were that simple. The sad truth is that sometimes, cyclical dependencies are not only necessary, they are actually quite essential and contribute greatly to making your code more readable and maintainable. The dangers of cyclical dependencies are well-known (stronger coupling, fragile build, etc...) as are the remedies (the introduction of an interface is usually a good start). For example, the following dependency:
public class Person {
public List<Account> getAccounts();
}
public class Account {
public Person getOwner();
}
can be refactored like this:
public interface IAccount {
}
public class Account implements IAccount {
}
public class Person {
public List<IAccount> getAccounts();
}
By introducing an interface, you have decoupled your two concrete classes, which is usually a good thing. The point of this post is not to offer a critique of this method but to bring up two points:
The second point is obvious: the perception of a "cyclical dependency" is very different in Java and, say, in C++, where you will usually declare and implement two coupled classed in the same file, thereby reducing the physical dependency (but not the logical one). The first point is more subtle. In the example code above, you now have three classes where you used to have two. And two of these three classes need to be kept in sync (IAccount must be modified whenever you modify Account). As I said earlier, it is usually a good thing, but not always, as was the case with Mike's JobDispatcher example. I find that most of the time, cycles are created as a consequence of a previous refactoring in which you have been breaking down one complex class into two smaller ones. There is clearly tension between two goals that are sometimes at odds: you can't always decrease both the complexity and the "coupledness" of your code (I know it's not a word, sorry, couldn't come up with a better one). If you improve one, you will probably make the other worse. Overall, introducing an interface in your code is usually the right choice, but there are some exceptions. Keep them in mind the next time someone is trying to convince you that your code is evil because two classes have a circular dependency on each other. Posted by cedric at November 17, 2004 10:30 AM Comments
To further expand your point, I think cyclical dependencies between "Class Categories" is not a problem. This is Booch speak for classes that live and work and are reused together. Ideally your class categories are modularized well so that these dependencies are within a particular package. The real maintenance issue is when these dependencies start to span package boundaries which is when refactoring becomes a good idea. Posted by: Brian Sletten at November 17, 2004 11:19 AMYou have a typo in your code example public class Account implements Account { should read: public class Account implements IAccount { Cedric, I wish upon you good tidings, and the patience of a saint when Michal reads this blog entry :-) I'm not entirely sure but isn't double dispatching in most object-oriented languages an example of circulare dependencies? The visitor pattern requires double dispatching in java from what I know. So, if this is all true than you have another argument against why circular dependencies is a very real need. Posted by: at November 17, 2004 12:31 PMI don't follow your example. Presumably some user of Account is calling getOwner or the whole thing is pointless. How's he supposed to get this information in your interfaced version? Posted by: Jonathan Ellis at November 17, 2004 02:23 PMUsing interface as much as possible is not only the right choice...but the better choice in order to reduce dependency cycles between classes. As Martin Fowler stated :"Putting abstract classes in supertype package is good way of breaking cycles in the dependency structure" This is based on Indirection Pattern.
public interface IAccount { public class Account implements IAccount { public class Person implements IPerson { Cedric! >Software fundamentalists are quick to point out >that if you see a cycle in your classes or your >packages, you should immediately remove it. Nope - there is no way to actually remove all of them! There is a primary reason why interfaces exists in Java in other languages http://java.sun.com/docs/books/tutorial/java/concepts/interface.html You can see that reason is to define - protocols/contracts which will be then implemented by classes. Good interface will expose as much of implementation details as one needs to know how to code only against the interface without knowing any internals of any class which will ever implement that interface. As somebody pointed in my discussion with Mike - there are such situation when there is no way to avoid cyclical dependencies between classes/interfaces. For example this may happen if you have to implement callback methods. Now the question is what is better: a) define precisely protocol/contract b) Hide the fact that such coupling will My subjective judgment is that a) is better as b) may lead more easily to broken contract when implementation of calle/caller implementation are not compatible.
When IT industry will be more matured - it could be that will see applications having 1000s of components and most likely (ideally) you will be using many components which were
For example for some reasons EJB should not use any class from java.io package, yet there is many application deployed with ejbs which perform blocking io operations. Why - and why do they work?
P.S. Mike - LOL :) Posted by: Michal at November 18, 2004 03:20 AMhi, Regards, Michal, as I mentioned in my blog the more specific you can be, the better people will understand where you're coming from. In this case you've provided enough background that the issue can actually be discussed rationally :-) You're talking about generic containers, components from many sources, and a sort of large scale component marketplace. This is of course a valid viewpoint. But it bears no resemblance to the sort of development I was talking about. My examples, and indeed most of my blog, talks about internal IT development of very large applications, where a team specifically owns a very large body of source code. The design imperatives in such a context are quite different from the component market place you envision. In my own group, it can be perfectly natural for developers to design components that are designed to work together in concert with each other, and even to have cyclical dependencies on each other. Even in a larger marketplace, a unified single component API may be exposed to the world by component XYZ, but unbeknownst to you that component may be internally organized with one or more groups of cooperating components internally. Cyclical dependencies may be inside of that blob of code called a "component", but that fact is hidden from you the user of the component. Likewise, in IT-developed code some "local" components may be defined in a cooperating, mutually depedendent manner, but _nobody cares but the writers of those cooperating components_. As such this can be a local design choice that has no impact on the large architecture or design. This is, I believe, the error in your assertions. You are asserting effectively that you should treat the large scale (thousands of components) identically to local, tactical development. I disagree - tactical concerns are quite different from sweeping architectural ones, and indeed good developers can take more liberties on the local scale and simultaneously hide this fact from higher level concerns. Software does not have to be a homogenous layer of components all existing at the same level as "peers". It can also be hierarchical layers of varying abstraction, and with different techniques applied at different levels. >This is, I believe, the error in your assertions. >You are asserting effectively that you should treat >the large scale (thousands of components) >identically to local, tactical development. I >disagree - tactical concerns are quite different >from sweeping architectural ones, and indeed good >developers can take more liberties on the local >scale and simultaneously hide this fact from higher >level concerns. No! I never expressed such opinion. There is for example nothing wrong with j2ee aplication having servlet and ejb containers (that's not my scenario) and with picocontainer embeded inside servlet container and spring inside ejb container. From the beging I was keep telling you that you should not try to generalize some rules (e.g. your rule that cyclical dependcies must be supported by good container) as in some context such rules are simply false or not important. Michal Posted by: Michal at November 18, 2004 11:24 AMThe example you provide is very granular and the inclusion of a circular reference seems natural as it captures an important circular relationship in the abstraction, ownership. So clearly in this example, circular dependencies are not only ok, they are quite necessary! But, when we increase the level of granularity beyond objects to components, then I would strongly object to the existance of circular dependencies. IME, circular dependencies in components indicate missing or the use of poor abstraction, or the an inability in the design to deliniate roles and responsibilities. I think metrics such as Demeter address this idea as it allows for couplings between closely related things and penalizes one for couplings between distantly related things. I wrote an bit on efferent and afferent couplings for IBM-DW a few months ago. In that article I used serialization to demonstrate the importance or the effects that these types of couplings can have on the overall maintainability of the code. Clearly, you can have as many circular dependencies in your code as you want. Similarly, the people maintaining serialization can do the same. The whole scheme breaks down if serialization allowed for a circular dependency to exists. For the circular dependency to exist, Sun would have to have knowledge of your code as you'd have to have knowledge of the serialization code. Not only does this constitue a serious violation of encapsulation, it is clearly unworkable. Posted by: Kirk at November 19, 2004 04:19 AMThe person-account example may suggest a situation which makes cyclic
public class Person { public class Account { public class Bank { } Certainly this is by no means a general solution. And this is exactly the point As stated already, the example given is one having an inherently tight and natural relationship, which might not be appropriate to abstract. It depends on what responsibilities the Account and Person types will have. Also, experts reject circular dependency most forcefully in broader contexts than class level. Circular dependency is always a blunder at component level. But the item I most wish to respond to is when you state, "your code is more decoupled but it's also more complex." Count-of-types is a poor measure of complexity. Count-of-relationships is overwhelmingly more telling a measure of complexity. (And we should really go further to speak of measuring number of specific categories of relationship, but we haven't the space here.) Post a comment
|