Паттерны проектирования - это Б А З А. Паттерны представляют типичные решения часто повторяющихся проблем. Они представляют не конкретный класс или функцию, а только концепцию для разработки своего решения.
Все примеры будут реализованы на React как самая популярная JS-библиотека Reference: Patterns.dev Книга "Погружение в паттерны проектирования [2018] Александр Швец"
Эти паттерны отвечают за удобное и безопасное создание новых объектов или даже целых семейств объектов.
Фабрика включает в себя функцию создания объекта. Фабрика полезна для создания сложные настраиваемые объекты. Также можно и простые объекты, которые могут делить с собой общие свойства. создавать через фабрику
const createUser = ({ firstName, lastName, email }) => ({
firstName,
lastName,
email,
fullName() {
return `${this.firstName} ${this.lastName}`;
},
});
Паттерн Prototype — это полезный способ совместного использования свойств многими объектами одного типа. Prototype — это нативный объект JavaScript, и к нему могут обращаться объекты через цепочку prototype.
Cоздаем класс Dog
.
class Dog {
constructor(name) {
this.name = name;
}
bark() {
return `Woof!`;
}
}
const dog1 = new Dog("Daisy");
const dog2 = new Dog("Max");
const dog3 = new Dog("Spot");
Все свойства, определенные в классе, автоматически добавляются в прототип
console.log(Dog.prototype);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()
console.log(dog1.__proto__);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()
Вместо того, чтобы каждый раз определять свойства объекта, можно добавить свойство к прототипу
class SuperDog extends Dog {
constructor(name) {
super(name);
}
fly() {
return "Flying!";
}
}
Данный прототип позволяет проще получать и наследовать свойства других объектов. Цепочка наследований уменьшает и количество кода, и объем занимаемой памяти.
Одиночка — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
Создаем класс-счетчик Counter
.
У этого класса объявлены следующие поля:
getInstance
- возвращает экземпляр классаgetConut
- возвращает текущее значение счетчикаincrement
- увеличение счетчикаdecrement
- уменьшение счетчика
let instance;
let counter = 0;
class Counter {
constructor() {
if (instance) {
throw new Error("You can only create one instance!");
}
instance = this;
}
getInstance() {
return this;
}
getCount() {
return counter;
}
increment() {
return ++counter;
}
decrement() {
return --counter;
}
}
const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;
Пусть у нас есть следующие файлы
counter.js
: содержит singletonCounterredButton.js
: Красная кнопка, которая импортирует счетчик и меняет егоblueButton.js
: Синяя кнопка, которая импортирует счетчик и меняет его
Когда мы вызываем метод инкрементации счетчика в красной или синей кнопки, меняется значение в счетчике, следовательно и в красной и синей кнопке получат новое значение счетчика
Во многом паттерн является оверхед и иногда глобальная доступность объекта может создавать неконтролируемые сайд-эффекты
Cтруктурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
Эти паттерны отвечают за построение удобных в поддержке иерархий классов.
Паттерн, позволяющий обернуть объект дополнительной логикой на получение или изменение его свойств.
Реализуем объект person
const person = {
name: "John Doe",
age: 42,
nationality: "American",
};
Вместо того, чтобы обращаться к объекту напрямую используем объект Proxy
const personProxy = new Proxy(person, {});
Второй аргумент у конструктора прокси это хендлер который позволяет реализовать различные воздействия на объект. Самые банальные set
и get
get
: Получение значение свойства объектаset
изменение состояния объекта
const personProxy = new Proxy(person, {
get: (obj, prop) => {
console.log(`The value of ${prop} is ${obj[prop]}`);
},
set: (obj, prop, value) => {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
obj[prop] = value;
},
});
Прокси-объекты полезны для различной валидации или логировании Cтоит помнить, что чрезмерное использование прокси-объекта или выполнение сложных операций при каждом вызове метода обработчика может легко негативно сказаться на производительности вашего приложения.
Легковес полезен при создании большого количества одинаковых объектов
Создаем библиотеку. Каждая книга в библиотеке имеет следующую структуру:
class Book {
constructor(title, author, isbn) {
this.title = title;
this.author = author;
this.isbn = isbn;
}
}
Стоит иметь в виду, что в библиотеке хранятся несколько экземпляров книг, но зачем создавать отдельные сущности одинаковых объектов. Для этого используем данный паттерн. Создаем функцию добавления книг в библиотеку.
const books = new Map();
const createBook = (title, author, isbn) => {
const existingBook = books.has(isbn);
if (existingBook) {
return books.get(isbn);
}
};
Если книги нет в наличии, то добавляем ее в перечень
const createBook = (title, author, isbn) => {
const existingBook = books.has(isbn);
if (existingBook) {
return books.get(isbn);
}
const book = new Book(title, author, isbn);
books.set(isbn, book);
return book;
};
Создаем метод на добавление экземпляра уже существующей книги
Метод addBook
будет получать ссылку на уже существующую в isbnNumbers
книгу и добавлять ссылку в массив доступных экземпляров книг bookList
const bookList = [];
const addBook = (title, author, isbn, availability, sales) => {
const book = {
...createBook(title, author, isbn),
sales,
availability,
isbn,
};
bookList.push(book);
return book;
};
Таким образом, мы имеем 5 копий от 3-х существующих книг
addBook("Harry Potter", "JK Rowling", "AB123", false, 100);
addBook("Harry Potter", "JK Rowling", "AB123", true, 50);
addBook("To Kill a Mockingbird", "Harper Lee", "CD345", true, 10);
addBook("To Kill a Mockingbird", "Harper Lee", "CD345", false, 20);
addBook("The Great Gatsby", "F. Scott Fitzgerald", "EF567", false, 20);
console.log("Total amount of copies: ", bookList.length);//5
console.log("Total amount of books: ", isbnNumbers.size);//3
- Вы всегда должны помнить о том, что Легковес применяется в программе, имеющей громадное количество одинаковых объектов. Этих объектов было так много, что они не помещались в доступную оперативную память без ухищрений. Паттерн разделил данные этих объектов на две части — контексты и легковесы.
- Легковес содержит состояние, которое повторялось во множестве первоначальных объектов. Один и тот же легковес можно использовать в связке с множеством контекстов. Состояние, которое хранится здесь, называется внутренним, а то, которое он получает извне — внешним
- Контекст содержит «внешнюю» часть состояния, уникальную для каждого объекта. Контекст связан с одним из объектов-легковесов, хранящих оставшееся состояние
- Поведение оригинального объекта чаще всего оставляют в Легковесе, передавая значения контекста через параметры методов. Тем не менее, поведение можно поместить и в контекст, используя легковес как объект данных
- Клиент вычисляет или хранит контекст, то есть внешнее состояние легковесов. Для клиента легковесы выглядят как шаблонные объекты, которые можно настроить во время использования, передав контекст через параметры.
- Фабрика легковесов управляет созданием и повторным использованием легковесов. Фабрика получает запросы, в которых указано желаемое состояние легковеса. Если легковес с таким состоянием уже создан, фабрика сразу его возвращает, а если нет — создаёт новый объект
;
Эти паттерны решают задачи эффективного и безопасного взаимодействия между объектами программы.
При реализации паттерна наблюдателя определяются:
observers
- объекты, подписанные на другой объект, которые получают данные через подпискуobservable
- объект, который щарит данные для своих подписчиков
observable
объект обычно содержит следующие поля:
observers
: список подписчиковsubscribe()
: метод, который добавляет функцию-коллбек в массив подписчиковunsubscribe()
: удаление из массива подписчиковnotify()
: уведомление всех подписчиков
class Observable {
constructor() {
this.observers = [];
}
subscribe(func) {
this.observers.push(func);
}
unsubscribe(func) {
this.observers = this.observers.filter((observer) => observer !== func);
}
notify(data) {
this.observers.forEach((observer) => observer(data));
}
}
Полезный паттерн, повсеместно используемый. На базе этого паттерна реализована библиотека RxJS
Паттерн посредника позволяет наладить общение компонентов через единую точкку-объект или функцию. Вместо: Получается это Вместо того, чтобы позволять каждому объекту напрямую взаимодействовать с другими объектами, что приводит к связи "многие ко многим", запросы объекта обрабатываются посредником. Посредник обрабатывает этот запрос и отправляет его туда, куда должен.
Примером данного паттерна групповой чат. Пользователи не хотят общаться напрямую, они отправляют сообщения в групповой чат
class ChatRoom {
logMessage(user, message) {
const time = new Date();
const sender = user.getName();
console.log(`${time} [${sender}]: ${message}`);
}
}
class User {
constructor(name, chatroom) {
this.name = name;
this.chatroom = chatroom;
}
getName() {
return this.name;
}
send(message) {
this.chatroom.logMessage(this, message);
}
}
Это поведенческий паттерн проектирования, который позволяет передавать запросы последовательно по цепочке обработчиков. Каждый последующий обработчик решает, может ли он обработать запрос сам и стоит ли передавать запрос дальше по цепи.