I am not sure which of the circular dependencies you are concerned with, there are at least three:
- Compile time circular dependency.
- Memory/Reference circular dependency (each object holds a reference to the other).
- Construction circular dependency (you cannot immutable create both objects using only the constructors).
Taking each in turn:
1. Compile Time
There is a pattern (some would say anti-pattern) of creating one or more interfaces for every single object. Given this, it is possible for two interfaces to have a circular dependency (each interface could have a method which takes the other interface as a parameter), however all concrete objects only reference interfaces, so there is no compile time dependency between the concrete classes.
In a real world program I would not recommend going this far, however the thought experiment of what this does to your code is useful, for example (using the I
prefix to indicate an interface) means that:
Gun
only depends on IReloadSystem
ReloadSystem
only depends upon IGun
.
You can create multiple classes that implement IGun
and multiple classes that implement IReloadSystem
- hence no Gun
is tied to any particular ReloadSystem
2. Memory/Reference
In Java you typically don't have to worry about reference counting / freeing memory (the garbage collection typically manages that for you). Hence references to other objects are typically just a short hand for control flow:
If I call a method on object (A) and (A) has a reference (direct or indirect) to object (B) there is a possibility that a method on (B) gets invoked. Additionally if both (A) and (B) have references to each other there is a possibility of an infinite loop / stack overflow - if each object calls the other one.
In your case a Gun
and a ReloadSystem
are tightly intertwined - if I was reading your code I would not be surprised if they invoked each other - in short I don't see it as a problem that they call each other.
That said a Gun
is probably a collection of functionalities, so it is likely as you refactor it that Gun
simply becomes a (top level) collection and it is other subsystems (of the gun) that have the circular reference dependencies between them.
3. Object Construction
If I only define constructors as follows, for two Java objects:
class A {
A(B b) {}
}
class B {
B(A a) {}
}
It becomes (almost **) impossible to construct them since to construct the first one and I need to construct the other one first, hence there is a catch 22.
** - Some inversion of control frameworks can create a dynamic proxy to resolve this constraint, but it is bad practice, since you still have the same problem when you want to test your classes.
Hence the better solution is simply to add a setB(B b)
method on class A, so that you can construct (A) first, construct (B) then call setB()
on (A) to finish the construction - if you go this path I would suggest using a factory to construct both objects.
Summary
I would probably introduce interfaces, so that you don't tie reload systems to particular guns. Then I would add setReloadSystem()
methods to each of the guns - as it makes a little more sense to me to construct the Gun first before constructing the ReloadSystem
, but your milage may vary on that point.