Mediator

Wzorce projektowe

  |   9 min czytania

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.

Mediator diagram

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.