DEV Community

Cover image for Mediator design pattern: En dos aplicaciones
Sergio
Sergio

Posted on

Mediator design pattern: En dos aplicaciones

El patrón de diseño Mediador (Mediator design pattern) es un patrón de comportamiento que permite crear interacciones entre objetos reduciendo un excesivo acoplamiento.

Según definición en Wikipedia

"El patrón mediador define un objeto que encapsula cómo un conjunto de objetos interactúan. Este patrón de diseño está considerado como un patrón de comportamiento debido al hecho de que puede alterar el comportamiento del programa en ejecución."

Esquema básico UML de clases del patrón Mediator

Cuando indagué más sobre este patrón, me encontré con una potente herramienta con la que contar a la hora de solucionar problemas como los que describiré en este artículo.

Ejemplo básico

Un ejemplo sería una sala de chat, donde existen múltiples usuarios que pueden comunicarse públicamente o de manera privada (mensaje privado). La clave está en delegar al Mediador la responsabilidad de los sucesos, desacoplando la interacción entre el resto de instancias.

Un user(User) recibirá la interfaz de Chatroom y podrá ordenarle:

  • Quiero enviar un mensaje global
  • Quiero enviar un mensaje a un usuario en concreto
  • Quiero mutear los mensajes que provean de estos usuarios

Y un chatroom(Chatroom) sabrá procesar estas peticiones. Si esta petición involucra a otras entidades relacionadas con el Mediador, una acción colateral se verá ejecutada de manera invisible al user(User)

// 'chatroom.js'

function Chatroom() {
    this.users = {};
    this.mutes = {};
}

Chatroom.prototype.register = function(user) {
    this.users[user.nickname] = user;
    user.chatroom = this;
}

Chatroom.prototype.unregister = function(user) {
    // this.users ...
}

Chatroom.prototype.deliver = function(message, from, to) {
    if (to && !this.hasMuted(to, from)) { // Comprueba si existe un muteo y envía un mensaje privado
        this.users[to].receive(message, from);
    } else { // Comprueba si existe un muteo y envía un mensaje a todos los usuarios
        for (key in this.users) {
            if (this.users[key] !== from && !this.hasMuted(this.users[key], from)) {
                this.users[key].receive(message, from);
            }
        }
    }
}

Chatroom.prototype.mute = function(receiver, sender) {
    // Prohibe a 'sender' enviar mensajes a 'receiver'
}

Chatroom.prototype.hasMuted = function(receiver, sender) {
    // Comprueba si 'receiver' tiene muteado a 'sender'
    return false;
}
// 'user.js'

function User(nickname) {
    this.nickname = nickname; 
    this.chatroom = undefined;
}

User.prototype.send = function(message, to) {
    this.chatroom.deliver(message, this.nickname, to);
}

User.prototype.receive = function(message, from) {
    console.log(`(${this.nickname}) [${from}]: ${message}`);
}

User.prototype.muteFrom = function(user) {
    // this.chatroom.mute ...
}

User.prototype.disconnect = function() {
    // this.chatroom.unregister ...
}
// 'main.js'

const mainChatroom = new Chatroom();
const offTopicChatroom = new Chatroom();

const sergio = new User('Sergio999');
mainChatroom.register(sergio);

const jose = new User('x0s3');
mainChatroom.register(jose);

const manel = new User('manel');
mainChatroom.register(manel);

sergio.send('Hola a todos');
// (x0s3) [Sergio999]: Hola a todos
// (manel) [Sergio999]: Hola a todos

sergio.send('Hola Manel!', 'manel');
// (manel) [Sergio999]: Hola Manel!

Me gusta imaginar el patrón como un mensajero, y qué mejor ejemplo. El usuario sabe qué acciones puede realizar, pero el mensajero (Mediador) es el que sabe cómo realizarlas. Es como entregarle un bulto con una información y decirle: "Ya sabes lo que toca hacer", o bien preguntarle cualquier cosa que sea capaz de responderte.

En el anterior ejemplo, se intercomunican instancias del mismo tipo (User). Sin embargo podrían ser de cualquier otro tipo, como por ejemplo una torre de control donde pueden comunicarse tanto Aviones, Pilotos, Operarios de tierra, etc...

Aquí todavía no ha llegado Tsunami Democratic

No voy a detallar una implementación de la torre de control, dado que sería muy parecido. Voy a pasar a otro caso de uso muy destacable.

Uso como Workflow de eventos

Otro uso que se le puede dar al patrón Mediator es como desarrollo de un workflow, dado de que se parte del concepto de Mediator como una figura que toma el control de acciones para inter-desacoplar los objetos asociados a él.

En el siguiente ejemplo tomamos VideoProcessorWorkflow como el mediador de los eventos. Sus colleages (elemento en el UML de Mediator Design Pattern) serán instancias de módulos con lógica totalmente encapsulada y aislada a través de cada una de sus interfaces:

// 'videoprocessor.workflow.js'

function VideoProcessorWorkflow(
    video,
    { videoConverter, videoFXApplier, videoUploader },
    opts
) {
    const { emit, on, once } = new EventEmitter();
    // Exposing public members
    this.on = on;
    this.once = once;

    // Defining the workflow. Callback style for the glory
    videoConverter.exec(video, opts.converter).once('video/converted', (err, video => {
        videoFXApplier.exec(video, opts.fx).once('video/fxed', (err, video) => {
            videoUploader.exec(video, opts.upload).once('video/uploaded', (err, link) => {
                // Workflow emits a result event
                emit('videoProcessorWorkflow/completed', link);
            });
        });
    }));
}

VideoProcessorWorkflow será una función constructora que expondrá los métodos .on() y .once() a los que poder añadir handlers.

Por otra parte tenemos features/componentes/modulos que contienen lógica totalmente aislada, pero que podemos utilizar a través de un proceso como el Workflow que acabamos de desarrollar.

// 'modules/index.js'

/**
 * Módulos totalmente desacoplados que emiten eventos
 */

function VideoConverterModule(video, opts) {
    // Implementation
}

function VideoFXApplierModule(video, opts) {
    // Implementation
}

function VideoUploaderModule(video, opts) {
    // Implementation
}

Y finalmente lo que vendría a ser el main(), contenedor principal o controlador que orquesta el workflow y sus dependencias.

// 'main.js'

const video = 'file.avi';

const modules = {
    videoConverter: new VideoConverterModule(),
    videoFXApplier: new VideoFXApplierModule(),
    videoUploader: new VideoUploaderModule()
};

const opts = {
    converter: {
        outputFormat: 'mp4'
    },
    fx: {
        bright: -1
    },
    upload: {
        to: 'youtube',
        description: '...'
    }
};


const videoProcessorWorkflow = new VideoProcessorWorkflow(video, modules, opts);

videoProcessorWorkflow.on('videoProcessorWorkflow/completed', (err, link) => {
    console.log(`Video uploaded to: ${link}`);
    process.exit(0);
});

Con este patrón se podrían seguir creando más Workflows para la digestión de eventos, como por ejemplo sería un VideoProcessorErrorWorkflow que encadene una serie de sucesos a raíz de un error en alguno de los módulos.

Hasta aquí mi aportación del día, espero que te haya sido de utilidad!

Top comments (0)