Mediator

Mediator Pattern

Introduction

The Mediator pattern is a behavioral design pattern that reduces coupling between classes by providing a central authority through which different components may communicate and interact. It’s particularly useful in scenarios where a system has multiple interacting components, and you want to centralize complex communications and control mechanisms.

Purpose

  • To centralize complex communications and control between related objects.
  • To reduce coupling between classes that communicate with each other.

Use Cases

  • When a set of objects communicate in well-defined but complex ways, the components become mutually dependent.
  • To centralize control and communication between related components in a single mediator object.

Advantages/Disadvantages

Advantages:

  • Reduces direct communications between the components and limits dependencies.
  • Centralizes control in one location.
  • Simplifies maintenance and interactions between components.

Disadvantages:

  • The mediator can become overly complex, taking on too many responsibilities.
  • May lead to a ‘god object’ antipattern if not implemented carefully.

Implementation in JavaScript

javascript
	class Mediator {
	  notify(sender, event) {}
	}
	
	class ConcreteMediator extends Mediator {
	  constructor(component1, component2) {
	    super();
	    this.component1 = component1;
	    this.component1.setMediator(this);
	    this.component2 = component2;
	    this.component2.setMediator(this);
	  }
	
	  notify(sender, event) {
	    if (event === 'A') {
	      console.log('Mediator reacts on A and triggers following operations:');
	      this.component2.doC();
	    } else if (event === 'D') {
	      console.log('Mediator reacts on D and triggers following operations:');
	      this.component1.doB();
	      this.component2.doC();
	    }
	  }
	}
	
	class BaseComponent {
	  constructor(mediator = null) {
	    this.mediator = mediator;
	  }
	
	  setMediator(mediator) {
	    this.mediator = mediator;
	  }
	}
	
	class Component1 extends BaseComponent {
	  doA() {
	    console.log('Component 1 does A.');
	    this.mediator.notify(this, 'A');
	  }
	
	  doB() {
	    console.log('Component 1 does B.');
	    this.mediator.notify(this, 'B');
	  }
	}
	
	class Component2 extends BaseComponent {
	  doC() {
	    console.log('Component 2 does C.');
	    this.mediator.notify(this, 'C');
	  }
	
	  doD() {
	    console.log('Component 2 does D.');
	    this.mediator.notify(this, 'D');
	  }
	}
	
	// Usage
	const c1 = new Component1();
	const c2 = new Component2();
	const mediator = new ConcreteMediator(c1, c2);
	
	console.log('Client triggers operation A.');
	c1.doA();
	
	console.log('Client triggers operation D.');
	c2.doD();

Practical Example

Imagine a scenario in a backend system where various services like a database service, logging service, and authentication service need to interact with each other. Using a mediator can simplify these interactions.

javascript
	class DatabaseService {
	  save(data) {
	    console.log(`Saving data: ${data}`);
	  }
	}
	
	class LoggingService {
	  log(message) {
	    console.log(`Logging: ${message}`);
	  }
	}
	
	class AuthenticationService {
	  authenticate(user) {
	    console.log(`Authenticating user: ${user}`);
	    return true;
	  }
	}
	
	class SystemMediator {
	  constructor() {
	    this.databaseService = new DatabaseService();
	    this.loggingService = new LoggingService();
	    this.authenticationService = new AuthenticationService();
	  }
	
	  handle(event, data) {
	    switch (event) {
	      case 'save':
	        this.databaseService.save(data);
	        break;
	      case 'log':
	        this.loggingService.log(data);
	        break;
	      case 'authenticate':
	        return this.authenticationService.authenticate(data);
	      default:
	        console.log('Unknown event');
	    }
	  }
	}
	
	// Usage
	const mediator = new SystemMediator();
	
	mediator.handle('authenticate', 'User123');
	mediator.handle('save', 'UserData');
	mediator.handle('log', 'User authenticated and data saved');

In this example, SystemMediator acts as a central hub for different services, thus simplifying their interactions and reducing direct dependencies among them.