Zastosowanie
Mediator
(ang. Mediator
) (wzorzec behawioralny) ma za zadanie ułatwić i usprawnić komunikacje między grupą obiektów przy jednoczesnym zmniejszeniu zależności między klasami i ich instancjami. Obiekty kolegów Colleague
rejestrują się do Mediatora
. Mediator przechowuje referencje do grupy kolegów - obiektów zainteresowanych komunikacją. Gdy dany kolega nadaje komunikator to mediator decyduje o tym do kogo z grupy kolegów ma on trafić. Koledzy
nie mają zatem wiedzy o innych obiektach, a całą logiką komunikacji zajmuje się Mediator
. Można powiedzieć, że Mediator
jest pośrednikiem między rozmówcami.
Ograniczenia
W dużych projektach klasy Mediatorów
mogą stać się zbyt złożone. Istnieje ryzyko, że klasa Mediatora
stanie się super klasą
(god object
). Ponadto przy niejednoznacznej implementacji nadany komunikat może być niejasny. Może zajść potrzeba użycia instrukcji warunkowych oraz opakowania danych w klasę wrapper
co utrudnia modyfikację i rozszerzalność. Jeśli obiekt Mediatora
jest także odbiorcą komunikatów kolegów wówczas może dojść do zapętlenia. Z uwagi na to, że każdy komunikat musi przejść przez Mediatora
, można odnotować spadek wydajności systemu.
Użycie
Mediator
stosowany jest tam gdzie wiele obiektów o wspólnym interfejsie musi komunikować się między sobą, a przechowywanie referencji we wszystkich obiektach staje się zbyt kosztowne.
Implementacja
Konkretny mediator ConcreteMediator
implementuje interfejs Mediator
oraz przechowuje grupe obiektów zainteresowanych komunikacją. Może przechowywać je w kolekcji czy też jako pola, jednak to drugie rozwiązanie ogranicza Mediator
do komunikacji dla zdefiniowanej liczby i nierzadko typów obiektów przez co jego użycie jest ściśle zdefiniowane. Mediator
rozsyła komunikacje na podstawie typów klas, pól obiektów kolegów oraz informacji o tym kto jest nadawcą. Obiekty kolegów rozszerzają klasę Colleague
i przechowują referencje do Mediatora
. Referencja może być ustawiana w konstruktorze (wymagana jest wtedy znajomość mediatora przed stworzeniem obiektu) lub przez Mediator
w trakcie rejestracji obiektu Colleague
.
Poniższy listing przedstawia implementacje Mediatora
oraz klas kolegów zainteresowanych komunikacją.
public class ConcreteMediator implements Mediator {
private List<Colleague> colleagues;
public ConcreteMediator() {
this.colleagues = new ArrayList();
}
public void register(Colleague colleague) {
colleagues.add(colleague);
}
@Override
public void send(Colleague colleague, Object args) {
//find receivers by type, variables or other things depends on way of storing colleagues
if(colleague instanceof Colleague1) {
if(args instanceof String)
notifyAll(Colleague2.class, args);
else if(args instanceof Integer)
notifyAll(Colleague3.class args);
}
else if(colleague instanceof Colleague2) {
if(args instanceof String)
notifyAll(Colleague3.class, args);
else if(args instanceof Integer)
notifyAll(Colleague1.class args);
}
else if(colleague instanceof Colleague3) {
notifyAll(Colleague.class, args);
}
}
private void notifyAll(Class type, Object args) {
for(Colleague colleague : colleagues) {
if(type.isInstance(colleague))
colleague.receive(args);
}
}
}
public class Colleague1 implements Colleague {
public Colleague1(Mediator mediator) {
super(mediator);
this.mediator.register(this);
}
@Override
public void receive(Object args) {
if(args instanceof String) {
//do action
}
else if(args instanceof Integer) {
//do action
}
else {
//do action
}
}
public void setText(String text) {
//do something inside class with text
mediator.send(this, text);
}
//other methods
}
//implementation of other colleagues in similar way
public interface Mediator {
void send(Colleague colleague, Object args);
}
public abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public abstract void receive(Object args);
}
Rejestracja i komunikacja między obiektami odbywa się w następujący sposób.
Mediator mediator = new ConcreteMediator();
//create colleagues and register
Colleague colleague1 = new Colleague1(mediator);
Colleague colleague2 = new Colleague2(mediator);
Colleague colleague3 = new Colleague3(mediator);
((Colleague1)colleague1).setText("Example"); //only colleague2 receive text
Przykład
Aplikacja Chat
umożliwia komunikację między użytkownikami User
. Wiadomości trafiają do Chat
managera, który na podstawie zawartych metadanych, rozsyła wiadomość do docelowych odbiorców. Konto moderatora Admin
, służy właścicielowi aplikacji do komunikacji z użytkownikami za pomocą wiadomości “do wszystkich”. Poniższy listing przedstawia implementację aplikacji z wykorzystaniem wzorca Mediator
.
public class Chat implements Mediator {
private List<User> users;
private Admin admin;
public Chat() {
this.users = new ArrayList();
}
public void register(User user) {
users.add(user);
}
public void registerAdmin(Admin admin) {
this.admin = admin;
}
@Override
public void send(Colleague user, Message message) {
if(user instanceof Admin) {
for(User recipient : users)
recipient.receive(message.getText());
}
else {
if(message.getRecipient().equals("admin"))
admin.receive(message);
else {
for(User recipient : users) {
if(recipient.getName().equals(message.getRecipient())) {
recipient.receive(message);
break;
}
}
}
}
}
}
public class Admin implements Colleague {
public User(Mediator mediator) {
super(mediator);
this.mediator.registerAdmin(this);
}
@Override
public void receive(Message message) {
//show message
//log
}
//other methods
}
public class User implements Colleague {
private String name;
//other fields
public User(String name, Mediator mediator) {
super(mediator);
this.mediator.register(this);
this.name = name;
}
@Override
public void receive(Message message) {
if(message.getFile() != null) {
//show file
}
else {
//show text message
}
}
public void getName() {
return name;
}
//other methods
}
public interface Mediator {
void send(Colleague colleague, Object args);
}
public abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public void sendMessage(Message message) {
mediator.send(this, message);
}
public abstract void receive(Message message);
}
Klient tworzy manager Chat
i dołącza do niego pojawiających się użytkowników.
Mediator chat = new Chat();
Colleague admin = new Admin(chat);
Colleague user1 = new User("user1", chat);
Colleague user2 = new User("user2", chat);
Colleague user3 = new User("user3", chat);
admin.sendMessage("Message for all users"); //all users receive message
user1.sendMessage("Hey", "user2"); //only user2 receives message
user2.sendMessage("Hey, how are you?", "user1"); //only user1 receives message
Biblioteki
Strategia implementacji Mediatora
jest podejmowana podstawie danej sytuacji. Może być ograniczona do sztywno zdefiniowanych obiektów wewnątrz Mediatora
lub przyjmować postać bardziej ogólną dostępną dla większej ilości obiektów. Ponadto rozsyłanie komunikacji również zmienia się w zależności od przyjętej strategii. W związku z tym nie używa się zewnętrznych bibliotek implementujących wzorzec Mediator
. Przykładem implementacji wzorca Mediator w Javie
może być klasa Timer
czy też Executor
.