DEV Community

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

Posted on

1 2

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!

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

AWS Q Developer image

Your AI Code Assistant

Ask anything about your entire project, code and get answers and even architecture diagrams. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Start free in your IDE

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay