Mediator encapsulates how a set of objects interact with each other. Due to this encapsulation, there is a loose coupling between the interacting objects.
Typically an object explicitly knows about other objects to which it wants to interact i.e. to call a method. In mediator pattern this interaction is within the mediator object and interacting objects only know about the mediator object.
The benefit of this arrangement is that the interactions can now change without needing modifications to participating objects. Changing the mediator allows adding/removing participants in an interaction.
2. Description
2.1 Implementation
Mediators define a generic method which is called by other objects
This method typically needs to know which object changed and optionally the exact property which has changed in that object
We implement this method in which we notify rest of the objects about the state change
Mediator needs to know about all participants in the collaboration it is mediating. To solve this problem we can either have objects register with mediator or mediator itself can be the creator of these objects.
Depending upon your particular implementation you may need to handle the infinite loop of change-notify-change which can result if object's value change handler is called for every value change whether from an external source as well as the mediator
2.2 Consideration
2.2.1 Implementation
It's important that mediator can identify which object has sent change notification to avoid sending that object the changed value again
If an object method took a very long time to process the change it can affect overall performance of mediator severely. In fact, this is a common problem in any notification system, so pay attention to synchronization in mediator methods.
We often end up with a complex mediator since it becomes a central point which ends up handling all routing between objects. This can make it very difficult to maintain the mediator as the complexity grows.
2.2.2 Design
We can extend a mediator and create variations to be used in different situations like platform-dependent interactions
Abstract mediator is often not required if the participating objects only work with that one mediator
We can use observer design pattern to implement the notification mechanism through which objects notify the mediator
2.3 Pitfalls
Mediator becomes a central control object. As complexity of interaction grows, mediator complexity can quickly get out of hand
Making a resuable mediator, one which can be used with multiple sets of different objects is quite difficult. They are typically very specific to the collaboration. Another competing pattern called Observer is much more reusable.
3. Usage
javax.swing.ButtonGroup class. It takes care of making sure that the only button in a group is selected. Participating Buttons notify this mediator when they are selected.
The DispatcherServlet in Spring
4. Comparison with Observer
Mediator
Observer
Intent is to encapsulate complex interaction between objects
Intent is to define one-to-many relationship between objects
Mediator implementations are typically specific to objects being mediated
Observer pattern implementations are generic. Once implemented it can be used with any classes.
5. Example
//Mediator
class UIMediator {
List<UIControl> colleagues = new ArrayList<>();
public void register(UIControl control) {
colleagues.add(control);
}
public void valueChanged(UIControl control) {
colleagues.stream().filter(c -> c != control).forEach(c -> c.controlChanged(control));
}
}
//Abstract colleague
interface UIControl {
void controlChanged(UIControl control);
String getControlValue();
String getControlName();
}
class TextBox extends TextField implements UIControl{
private UIMediator mediator;
private boolean mediateUpdate;
public TextBox(UIMediator mediator) {
this.mediator = mediator;
this.setText("Textbox");
this.mediator.register(this);
this.textProperty().addListener((v, o, n) -> {
if(!mediateUpdate)
this.mediator.valueChanged(this);
});
}
@Override
public void controlChanged(UIControl control) {
this.mediateUpdate = true;
this.setText(control.getControlValue());
this.mediateUpdate = false;
}
@Override
public String getControlValue() {
return getText();
}
@Override
public String getControlName() {
return "Textbox";
}
}
class Slider extends javafx.scene.control.Slider implements UIControl{
private UIMediator mediator;
private boolean mediatedUpdate;
public Slider(UIMediator mediator) {
this.mediator = mediator;
setMin(0);
setMax(50);
setBlockIncrement(5);
mediator.register(this);
this.valueProperty().addListener((v,o,n) ->{if(!mediatedUpdate) this.mediator.valueChanged(this);});
}
@Override
public void controlChanged(UIControl control) {
mediatedUpdate = true;
setValue(Double.valueOf(control.getControlValue()));
mediatedUpdate = false;
}
@Override
public String getControlName() {
return "Slider";
}
@Override
public String getControlValue() {
return Double.toString(getValue());
}
}
class Label extends javafx.scene.control.Label implements UIControl{
private UIMediator mediator;
public Label(UIMediator mediator) {
this.mediator = mediator;
this.setMinWidth(100);
this.setText("Label");
mediator.register(this);
}
@Override
public void controlChanged(UIControl control) {
setText(control.getControlValue());
}
@Override
public String getControlValue() {
return getText();
}
@Override
public String getControlName() {
return "Label";
}
}
public class Client extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
UIMediator mediator = new UIMediator();
Slider slider = new Slider(mediator);
TextBox box = new TextBox(mediator);
Label label = new Label(mediator);
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setVgap(20);
grid.setPadding(new Insets(25, 25, 25, 25));
grid.add(label, 0, 0);
grid.add(slider, 0, 1);
grid.add(box, 0, 2);
Scene scene = new Scene(grid, 500, 500);
primaryStage.setTitle("Mediator Pattern");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}