Skip to main content

Programming patterns

Programming patterns are solutions to common software design problems. They are provide a proven approach to solving common design issues. These patterns help developers create more flexible, reusable, and maintainable code.

Classification

Creational Patterns

These deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.

Examples: Singleton, Factory Method, Abstract Factory, Builder, Prototype.

Example: Factory Method

// Product classes
class Button {
render() {
console.log('Rendering a Button.');
}
}

class IconButton extends Button {
render() {
console.log('Rendering an IconButton.');
}
}

class ToggleButton extends Button {
render() {
console.log('Rendering a ToggleButton.');
}
}

// Factory
class ButtonFactory {
static createButton(type) {
switch (type) {
case 'icon':
return new IconButton();
case 'toggle':
return new ToggleButton();
default:
return new Button();
}
}
}

// Client code
const button1 = ButtonFactory.createButton('icon');
button1.render();

const button2 = ButtonFactory.createButton('toggle');
button2.render();

const button3 = ButtonFactory.createButton();
button3.render();

Structural Patterns

These deal with object composition, helping to ensure that if one part of a system changes, the entire system doesn’t need to do the same.

Examples: Adapter, Composite, Proxy, Flyweight, Facade.

Example: Facade

The Facade pattern hides the complexities of the system and provides an easy-to-use interface for the client.

In a web application, a Facade might simplify interactions with a suite of complex APIs by providing a single method that abstracts multiple underlying calls.

// Complex subsystem
class SubsystemA {
operationA() {
console.log('SubsystemA: Ready!');
}
}

class SubsystemB {
operationB() {
console.log('SubsystemB: Go!');
}
}

class SubsystemC {
operationC() {
console.log('SubsystemC: Fire!');
}
}

// Facade
class Facade {
constructor() {
this.subsystemA = new SubsystemA();
this.subsystemB = new SubsystemB();
this.subsystemC = new SubsystemC();
}

simplifiedOperation() {
this.subsystemA.operationA();
this.subsystemB.operationB();
this.subsystemC.operationC();
}
}

// Client code
const facade = new Facade();
facade.simplifiedOperation();

Behavioral Patterns

These deal with object collaboration and how responsibilities are distributed among objects.

Examples: Observer, Strategy, Command, Chain of Responsibility, Mediator.

Example: Observer

The Observer pattern defines a one-to-many dependency between objects. When one object changes state, all its dependents are updated. It allows a subject to notify observers about changes without knowing who or what those observers are.

Example: In JavaScript, the EventListener in the DOM API is an implementation of the Observer pattern.

// Subject
class Subject {
constructor() {
this.observers = [];
}

addObserver(observer) {
this.observers.push(observer);
}

removeObserver(observer) {
this.observers = this.observers.filter((obs) => obs !== observer);
}

notifyObservers() {
this.observers.forEach((observer) => observer.update());
}

changeState() {
console.log('Subject: State has changed!');
this.notifyObservers();
}
}

// Observer
class Observer {
update() {
console.log('Observer: Notified of state change!');
}
}

// Client code
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.changeState();