BehavioralTypeScriptverifiedVerified
Mediator Pattern in TypeScript
Defines an object that encapsulates how a set of objects interact, promoting loose coupling by keeping objects from referring to each other explicitly.
How to Implement the Mediator Pattern in TypeScript
1Step 1: Define the Mediator interface and participant base class
interface Mediator {
notify(sender: ChatParticipant, message: string): void;
}
abstract class ChatParticipant {
constructor(
protected readonly name: string,
protected mediator: Mediator
) {}
send(message: string): void {
console.log(`[${this.name}] sends: ${message}`);
this.mediator.notify(this, message);
}
receive(from: string, message: string): void {
console.log(`[${this.name}] received from ${from}: ${message}`);
}
}
class User extends ChatParticipant {}2Step 2: Implement the ChatRoom as a concrete mediator
class ChatRoom implements Mediator {
private participants: ChatParticipant[] = [];
join(participant: ChatParticipant): void {
this.participants.push(participant);
}
notify(sender: ChatParticipant, message: string): void {
for (const participant of this.participants) {
if (participant !== sender) {
(participant as User).receive(
(sender as unknown as { name: string }).name,
message
);
}
}
}
}3Step 3: Connect participants and exchange messages
const room = new ChatRoom();
const alice = new User("Alice", room);
const bob = new User("Bob", room);
const carol = new User("Carol", room);
room.join(alice);
room.join(bob);
room.join(carol);
alice.send("Hello everyone!");
// Bob received from Alice: Hello everyone!
// Carol received from Alice: Hello everyone!// ── Form Validation Mediator ──────────────────────────────────────
interface ValidationRule {
validate(value: string): string | null; // returns error message or null
}
interface FieldState {
value: string;
errors: string[];
isDirty: boolean;
isValid: boolean;
}
interface FormMediator {
fieldChanged(fieldName: string, value: string): void;
getFieldState(fieldName: string): FieldState;
isFormValid(): boolean;
}
// ── Field Component ───────────────────────────────────────────────
class FormField {
private state: FieldState = {
value: "",
errors: [],
isDirty: false,
isValid: false,
};
constructor(
public readonly name: string,
private mediator: FormMediator,
private rules: ValidationRule[]
) {}
setValue(value: string): void {
this.state = { ...this.state, value, isDirty: true };
this.mediator.fieldChanged(this.name, value);
}
validate(): string[] {
return this.rules
.map(rule => rule.validate(this.state.value))
.filter((msg): msg is string => msg !== null);
}
setState(state: Partial<FieldState>): void {
this.state = { ...this.state, ...state };
}
getState(): FieldState { return this.state; }
}
// ── Validation Rules ──────────────────────────────────────────────
const required: ValidationRule = {
validate: v => v.trim() ? null : "This field is required",
};
const email: ValidationRule = {
validate: v => /^[^s@]+@[^s@]+.[^s@]+$/.test(v) ? null : "Invalid email address",
};
const minLength = (min: number): ValidationRule => ({
validate: v => v.length >= min ? null : `Minimum ${min} characters required`,
});
// ── Form Mediator (Coordinator) ───────────────────────────────────
class RegistrationFormMediator implements FormMediator {
private fields = new Map<string, FormField>();
register(field: FormField): void {
this.fields.set(field.name, field);
}
fieldChanged(fieldName: string, _value: string): void {
const field = this.fields.get(fieldName);
if (!field) return;
const errors = field.validate();
field.setState({ errors, isValid: errors.length === 0 });
// Cross-field coordination: confirm password must match password
if (fieldName === "password" || fieldName === "confirmPassword") {
this.validatePasswordMatch();
}
}
private validatePasswordMatch(): void {
const password = this.fields.get("password");
const confirm = this.fields.get("confirmPassword");
if (!password || !confirm) return;
const confirmState = confirm.getState();
if (!confirmState.isDirty) return;
const passwordMatch = password.getState().value === confirmState.value;
const extraError = passwordMatch ? [] : ["Passwords do not match"];
const baseErrors = confirm.validate();
confirm.setState({ errors: [...baseErrors, ...extraError], isValid: baseErrors.length === 0 && passwordMatch });
}
getFieldState(fieldName: string): FieldState {
return this.fields.get(fieldName)?.getState() ?? { value: "", errors: [], isDirty: false, isValid: false };
}
isFormValid(): boolean {
return [...this.fields.values()].every(f => f.getState().isValid);
}
}
// ── Setup ─────────────────────────────────────────────────────────
const mediator = new RegistrationFormMediator();
const emailField = new FormField("email", mediator, [required, email]);
const passwordField = new FormField("password", mediator, [required, minLength(8)]);
const confirmField = new FormField("confirmPassword", mediator, [required]);
mediator.register(emailField);
mediator.register(passwordField);
mediator.register(confirmField);
export { RegistrationFormMediator, FormField, required, email, minLength, type ValidationRule };Mediator Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Mediator Pattern in the Real World
“An air traffic control tower is the classic example. Instead of every plane communicating directly with every other plane—a chaotic and dangerous mess—all aircraft talk only to the control tower. The tower mediates all interactions, directing each plane based on the overall picture it maintains. Planes are decoupled from each other entirely.”