Patrones de diseño – Mediator

Hoy vamos a ver el patrón de comportamiento Mediator, o mediador, su objetivo es encapsular como interactúan varios objetos entre sí, lo que nos ayuda a tener un bajo acoplamiento ya que los consumidores no necesitan saber como tratar con otros objetos si no que hablarán con el Mediator para que haga este trabajo por ellos.

No lo confundamos con el Facade, se parece pero no es lo mismo. El Facade nos abstrae de tener que comunicarnos con varios sistemas para hace una operación, en lugar de esto ponemos una fachada que nos hace de interfaz para hablar con todos estos sistemas.

Otra diferencia entre el Mediator y el Facade es que en el Facade los objetos dentro de estos subsistemas no conocen al Facade, no saben que hay un ente superior gestionándolos. Sin embargo con el Mediator los componentes que gestiona lo conocen y lo utilizan para comunicarse con otros componentes.

Patrón Mediator en detalle

Un Mediator es como un director, recibe distintos inputs de sus subordinados y en función de estos inputs da las órdenes. Como ya os podéis imaginar, este director o Mediador, tendrá un fuerte acoplamiento con los componentes que tiene por debajo, pero estos componentes solo estarán acoplados al mediator y no entre ellos, con lo que tendrán un bajo acoplamiento.

Yo este patrón lo utilizo muchísimo para diseñar menús, básicamente tengo un Mediator que se encarga de gestionar los submenus que tiene por debajo y cuando hacemos click en algún botón le decimos al otro que reaccione y haga lo que debe hacer.

Vamos a ver cómo aplicarlo en el código pero antes un diagrama:

patron Mediator

Arriba de todo tenemos el MenuMediator, este mediator no tiene representación gráfica no es un menú como tal, simplemente es el director de estos menús.

Vemos que este Mediator se relaciona con dos menús, MainMenu y SettingsMenu, estos dos sí que tienen representación gráfica, son los menús en si y en Unity tendremos un GameObject con estos scripts y con sus componentes de botones, imágenes, etc.

Como véis, el Mediator conoce a los Menús pero los menús también conocen al Mediator, sin embargo los menús no se conocen entre ellos, con lo que mantenemos un bajo acoplamiento. Si el SettingsMenu cambia el MainMenu no se verá afectado, pero es posible que el Mediator sí tenga que cambiar.

Con esto lo que buscamos es el menor acoplamiento posible entre los componentes que gestiona el Mediator.

Flujo con un Mediador

¿Y cómo sería el flujo? Pongamos el ejemplo de que en el Menú principal tenemos un botón para empezar el juego y otro para ir a la pantalla de settings y en Unity tenemos serializados estos componentes.

Lo que pasará es que cuando pulsemos el botón de settings, el botón avisará al MainMenu, este avisará al Mediator y será el Mediator el encargado de decirle a un menú que se oculte y al otro que se muestre.

Mediator pattern

Si os fijáis, todos estos botones que vamos a serializar y la clase que los contiene también está actuando de mediadores, los botones conocen a su contenedor y el contenedor conoce a los botones y reaccionará cuando sean pulsados. Pero el botón de ir a settings no hablará directamente con el menú de settings para que se muestre.

Este mismo ejemplo lo vamos a implementar en Unity para que nos quede completamente claro el comportamiento del Mediator pattern.

Implementando un Mediator pattern con Unity

Empecemos creando unos menús básicos, el MainMenu y SettingsMenu. Estos menús además de su lógica interna recibirán al Mediador mediante una función de configuración, o en el constructor si fuera una clase de C# pura.

public class MainMenu : MonoBehaviour
{
    [SerializeField] private Button _startGameButton;
    [SerializeField] private Button _settingsButton;
    [SerializeField] private CanvasGroup _canvasGroup;
    private MenuMediator _mediator;

    private void Awake()
    {
        _startGameButton.onClick.AddListener(()=>_mediator.StartGame());
        _settingsButton.onClick.AddListener(()=>_mediator.MoveToSettingsMenu());
    }

    public void Configure(MenuMediator mediator)
    {
        _mediator = mediator;
    }
    
    public void Show()
    {
        _canvasGroup.DOFade(1.0f, 0.5f);
    }

    public void Hide()
    {
        _canvasGroup.DOFade(0.0f, 0.5f);
    }
}

Esta clase tendrá el botón para empezar el juego y para ir a settings y en su evento onClick lo único que hará es avisar al Mediador de lo que ha ocurrido, será el Mediador el que decida qué hacer.

mediator pattern unity
Representación del menú principal
public class SettingsMenu : MonoBehaviour
{
    [SerializeField] private Button _backButton;
    [SerializeField] private CanvasGroup _canvasGroup;
    private MenuMediator _mediator;

    private void Awake()
    {
        _backButton.onClick.AddListener(()=>_mediator.BackToMainMenu());
    }

    public void Configure(MenuMediator mediator)
    {
        _mediator = mediator;
    }

    public void Show()
    {
        _canvasGroup.DOFade(1.0f, 0.5f);
    }

    public void Hide()
    {
        _canvasGroup.DOFade(0.0f, 0.5f);
    }
}

Lo mismo ocurre con el menú de settings, el botón back simplemente avisará al Mediator y este ya decidirá qué hacer.

patrón mediador Unity
Representación del menú settings

Cuando el MenuMediator reciba el input de lo que ocurre en sus subordinados decidirá qué hacer, en este caso si pulsamos para ir a SettingsMenu lo que hará es ocultar el MainMenu y mostrar el SettingsMenu.

public class MenuMediator : MonoBehaviour
{
    [SerializeField] private MainMenu _mainMenu;
    [SerializeField] private SettingsMenu _settingsMenu;

    private void Awake()
    {
        _settingsMenu.Configure(this);
        _mainMenu.Configure(this);
        
        _settingsMenu.Hide();
    }

    public void BackToMainMenu()
    {
        _mainMenu.Show();
        _settingsMenu.Hide();
    }

    public void StartGame()
    {
        Debug.Log("¡A jugar!");
    }

    public void MoveToSettingsMenu()
    {
        _mainMenu.Hide();
        _settingsMenu.Show();
    }
}

Lo mismo ocurre cuando pulsamos para volver al MainMenu, ocultamos el SettingsMenu y Mostramos el MainMenu.

Puedes descargar el proyecto que hemos utilizado aquí y verlo en acción en este enlace.

Conclusión

Hemos visto como aplicar un Mediator para gestionar menús, pero lo podríamos aplicar para cualquier caso por ejemplo:

Imaginemos un juego de carreras donde tenemos los vehículos hechos por componentes. Tenemos el componente de las ruedas, el componente del acelerador y el componente del freno.

Cuando se acelera queremos que las ruedas vayan más rápido y que le de más potencia al vehiculo, pero cuando se frena queremos que la velocidad se reduzca.

Podríamos hacer que tanto el acelerador como el freno hablasen con las ruedas, con lo que los estaríamos acoplando y además tendrían que tener en cuenta si es una moto con 2 ruedas, un coche con 4, o lo que sea.

En lugar de esto podemos crear un Mediator, que lo podríamos llamar Vehículo y cuando se pulse el acelerador este avisará al vehículo y el vehículo sabrá las ruedas que tiene y se comunicará con ellas, lo mismo para el freno.

Con esto mantenemos un bajo acoplamiento entre componentes y es el Mediador el que se acopla con ellos, y si le damos una vuelta más, en lugar de acoplarse a las clases concretas podríamos utilizar interfaces o clases abstractas para que después nos sea más fácil de ampliar y modificar el vehículo.

Como veis el Mediator es un gran amigo del desacoplamiento así que no dudéis en llamarle cuando tengáis muchas clases hablando entre sí y queráis poner orden.

Otras entradas

Resumen
➤ Patrones de diseño - Mediator
Nombre del artículo
➤ Patrones de diseño - Mediator
Descripción
▷ El objetivo del PATRÓN MEDIATOR es encapsular como interactúan varios objetos entre sí, lo que nos ayuda a tener un bajo acoplamiento 👌.
Autor
Publisher Name
The Power Ups - Learning
Publisher Logo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *