Design patterns are reusable solutions to commonly occurring problems in programming. They are not finished code but rather concepts or templates that can be adapted to a specific task. They are usually divided into three main groups:
Creational: Responsible for flexible object creation.
Structural: Define how objects can be combined to form larger structures.
Behavioral: Responsible for effective communication between objects.
Ensures that a module has only one instance and provides a global access point to it. In a functional approach, this is achieved using a closure.
Feature: Useful for managing shared state (e.g., application configuration, logging service).
const AppConfig = (() => {
let instance;
function createInstance() {
const config = { theme: 'dark' };
return {
getConfig: () => config,
};
}
return {
getInstance: () => {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();
console.log(config1 === config2); // true
Allows dynamically adding new functionality to objects by wrapping them in decorator functions.
Feature: A flexible alternative to inheritance. Ideal for extending functionality without modifying the original object.
const createCoffee = () => ({
cost: () => 5,
});
// Decorator
const withMilk = (coffee) => ({
...coffee,
cost: () => coffee.cost() + 2,
});
// Decorator
const withSugar = (coffee) => ({
...coffee,
cost: () => coffee.cost() + 1,
});
let myCoffee = createCoffee();
myCoffee = withMilk(myCoffee);
myCoffee = withSugar(myCoffee);
console.log(myCoffee.cost()); // 8 (5 + 2 + 1)
Provides a unified and simplified facade function for interacting with a complex subsystem.
Feature: Hides complexity. For example, you can create a facade for working with browser APIs (DOM, Fetch, LocalStorage).
// Complex subsystem
const domApi = {
createElement: (tag) => { /* ... */ },
addStyle: (element, styles) => { /* ... */ },
};
const fetchApi = {
get: (url) => { /* ... */ },
};
// Facade
const createAppFacade = () => ({
fetchAndDisplay: (url, elementId) => {
const data = fetchApi.get(url);
const element = domApi.createElement('div');
// ... display logic
}
});
const app = createAppFacade();
// app.fetchAndDisplay('/api/data', '#root');
Creates a subscription mechanism that allows some objects (observers) to watch for changes in another object (the subject).
Feature: The foundation of reactive programming. Used in state management (Redux, MobX) and the browser’s event model.
const createNewsPublisher = () => {
let subscribers = [];
return {
subscribe: (observer) => {
subscribers.push(observer);
},
unsubscribe: (observer) => {
subscribers = subscribers.filter(obs => obs !== observer);
},
notify: (news) => {
subscribers.forEach(observer => observer.update(news));
},
};
};
const createReader = (name) => ({
update: (news) => {
console.log(`${name} read the news: ${news}`);
},
});
const publisher = createNewsPublisher();
const reader1 = createReader('Ivan');
const reader2 = createReader('Maria');
publisher.subscribe(reader1);
publisher.subscribe(reader2);
publisher.notify('A new version of JavaScript has been released!');
// Ivan read the news: A new version of JavaScript has been released!
// Maria read the news: A new version of JavaScript has been released!
Defines a family of similar algorithms (functions) and allows them to be passed as arguments, making them interchangeable.
Feature: Allows changing logic on the fly. For example, choosing a sorting method or a form validation method.
const validate = (strategy, value) => {
return strategy(value);
};
const isRequired = (value) => !!value;
const isEmail = (value) => /@/.test(value);
console.log(validate(isRequired, '')); // false
console.log(validate(isEmail, 'test@test.com')); // true