Nikos makes a good point (Software Engineering principle) about coupling. There is one way though, to accomplish the "spirit" of the first (simple) approach and not encroach upon this principle by using the Mediator pattern. As taken from wikipedia (which is referencing the GoF):
"The essence of the Mediator Pattern is to "define an object that encapsulates how a set of objects interact". It promotes loose coupling by keeping objects from referring to each other explicitly, and it allows their interaction to be varied independently. Client classes can use the mediator to send messages to other clients, and can receive messages from other clients via an event on the mediator class."
Here you can consider your Controllers as the clients. What you need to have happen then is use the mediator to mediate "conversations" between each other.
First create a mediator interface:
public interface IMediateControllers {
void registerController2(Controller2 controller);
void registerController3(Controller3 controller);
void controller2DoSomething();
void controller3OperateOn(String data);
}
And then a concrete mediator (as a Singleton)
public class ControllerMediator implements IMediateControllers {
private Controller2 controller2;
private Controller3 controller3;
@Override
void registerController2(Controller2 controller) {
controller2 = controller;
}
@Override
void registerController3(Controller3 controller) {
controller3 = controller;
}
@Override
void controller2DoSomething() {
controller2.doSomething();
}
void controller3OperateOn(String data) {
controller3.operateOn(data);
}
/**
* Everything below here is in support of Singleton pattern
*/
private ControllerMediator() {}
public static ControllerMediator getInstance() {
return ControllerMediatorHolder.INSTANCE;
}
private static class ControllerMediatorHolder {
private static final ControllerMediator INSTANCE = new ControllerMediator();
}
}
Now, since Controller1 has Controller2 and Controller3 injected (as noted in the fxml file), you could do the following in Controller1::initialize() method:
@Override
public void initialize(Url url, ResourceBundle resource) {
ControllerMediator.getInstance().registerController2(controller2Controller);
ControllerMediator.getInstance().registerController3(controller3Controller);
}
Now, anywhere you need Controller2 to communicate with Controller3, you simple use the mediator:
// ... somewhere in Controller2
ControllerMediator.getInstance().controller3OperateOn("my data");
and Controller 3 can communicate back to Controller2 using the same mediator:
// ... somewhere in Controller3
ControllerMediator.getInstance().controller2DoSomething();
Of course, this relies on Controller2 having implemented the doSomething()
operation and Controller3 having implemented the operateOn(String data)
operation.
The important thing is that you've decoupled Controller2 and Controller3 (they don't know about each other). I just used this pattern in a little project I'm working on right now (inspired by Nikos' first solution, but thinking immediately of the Mediator pattern to remove the coupling he (properly) griped about.