I'm trying to create and object and a component object that have bi-directional references to each other. In this example, I have a Bike class and a Wheel class. One option I considered (Option 1) is to have Bike create Wheel, then pass a reference of itself to Wheel in its constructor. However, I've read that I shouldn't be passing "this" outside of a constructor, and that it's better to create Wheel object outside of the Bike constructor. So I should create Wheel first, then pass it to Bike, then call setBike() method for Wheel (Option 2). It looks to me like Option 1 is the simplest way to create the bi-directional references between Bike and Wheel, but it also seems to be violating some design principles. Can someone explain why option 2 would be preferred over option 1?
Option 1:
public class Wheel {
private Bike bike;
Wheel(Bike bike) {
this.bike = bike;
}
}
public class Bike {
private Wheel wheel;
Bike() {
this.wheel = new Wheel(this);
}
}
Bike bike = new Bike();
Option 2 :
public class Wheel {
private Bike bike;
public void setBike(Bike bike) {
this.bike = bike;
}
}
public class Bike {
private Wheel wheel;
Bike(Wheel wheel) {
this.wheel = wheel;
}
}
Wheel wheel = new Wheel();
Bike bike = new Bike(wheel);
wheel.setBike(bike);
The first option is not as desirable as you think. Not only it passes this
whereas the object is not fully initialized : this.wheel = new Wheel(this);
while here it should not cause any trouble since that is a very simplified usage but this option creates also a design issue : the wheel dependency is hardcoded in the constructor. You cannot switch/mock the dependency instance.
In a general way, bidirectional relationship create some constraints that force you to do some design tradeoff : value setting in two times, no immutability, at least an exposed setter, and so for... Note that you can cope with some of these issues by introducing some additional abstractions.
But to do simple here creating at least one of the two objects "alone" is a right choice as showed in your second option. While you could spare the setter part invocation if you handle the mutual relationship in the constructor :
Wheel wheel = new Wheel();
Bike bike = new Bike(wheel);
// wheel.setBike(bike); // not required now
Where Bike is defined as :
Bike(Wheel wheel){
this.wheel = wheel;
wheel.setBike(this);
}
To go further, as explained above, you can reduce the coupling between the two classes and the way of relating the two instances by introducing some abstraction.
For example you could introduce a Bike and a Wheel interface that would be the only way which the clients will manipulate a Bike and a Wheel. In this way you could perform the bi-relationship and even a certain level of immutability under the hood with internal implementations that clients will never see (thanks to private
nested
classes).
When you develop an API, that is often a way to favor to ensure some invariants and rules.
The idea is that clients do just :
Bike bike = CarFactory.of("super bike", "hard wheel");
Wheel wheel = bike.getWheel();
Interfaces :
public interface Wheel {
String getBarProp();
Bike getBike();
}
public interface Bike {
String getFooProp();
Wheel getWheel();
}
And client API and not public implementations in CarFactory :
public class CarFactory {
private static class BikeDefaultImpl implements Bike {
private final String fooProp;
private Wheel wheel;
public BikeDefaultImpl(String fooProp) {
this.fooProp = fooProp;
}
@Override
public String getFooProp() {
return fooProp;
}
@Override
public Wheel getWheel() {
return wheel;
}
}
private static class WheelDefaultImpl implements Wheel {
private final String barProp;
private Bike bike;
public WheelDefaultImpl(String barProp) {
this.barProp = barProp;
}
@Override
public String getBarProp() {
return barProp;
}
@Override
public Bike getBike() {
return bike;
}
}
public static Bike of(String bikeProp, String wheelProp) {
BikeDefaultImpl bike = new BikeDefaultImpl(bikeProp);
WheelDefaultImpl wheel = new WheelDefaultImpl(wheelProp);
bike.wheel = wheel;
wheel.bike = bike;
return bike;
}
}
You would also notice that this
usage in the constructor is not any longer required in this way because we can access to fields of Bike and Wheel implementation freely in the internal.