Инкапсуляция и модули в JavaScript: принципы структурирования кода
#РазноеДля кого эта статья:
- Разработчики, работающие с JavaScript, желающие улучшить качество и структуру своего кода
- Технические архитекторы и лидеры разработки, заинтересованные в внедрении принципов инкапсуляции и модульности
- Студенты и профессионалы, углубляющие знания об ООП в JavaScript и современных практиках программирования
JavaScript эволюционировал из простого языка для валидации форм в мощный инструмент построения сложных приложений, где структурированный код — не роскошь, а необходимость. Разработчики, погружающиеся в хаос неорганизованного кода, теряют до 42% продуктивного времени на понимание и рефакторинг запутанной логики. Инкапсуляция и модульность — это не просто теоретические концепции из учебников по ООП, а практические подходы, позволяющие создавать масштабируемые решения, где код можно не только писать, но и поддерживать годами. Готовы превратить спагетти-код в элегантную архитектуру? 🚀
Инкапсуляция в JavaScript: концепция и преимущества
Инкапсуляция — один из фундаментальных принципов ООП, который позволяет ограничить доступ к внутренним механизмам объекта, предоставляя интерфейс для взаимодействия с ним. В JavaScript, языке с динамической типизацией и прототипным наследованием, инкапсуляция приобретает особое значение.
Представьте инкапсуляцию как черный ящик: пользователи знают, какие кнопки нажимать для получения результата, но внутренние механизмы скрыты. Это не просто сокрытие данных, а целостная концепция проектирования, где компоненты системы имеют четкие границы и интерфейсы взаимодействия.
Алексей Ветров, технический архитектор
Однажды мы унаследовали проект с 15000 строк монолитного кода в одном файле main.js. Каждое изменение превращалось в детективное расследование, а побочные эффекты всплывали в самых неожиданных местах. Мы потратили два месяца на рефакторинг с применением принципов инкапсуляции: выделили независимые модули, определили четкие интерфейсы взаимодействия, скрыли внутренние детали реализации.
Результат превзошел ожидания: время на внедрение новой функциональности сократилось на 68%, количество регрессионных багов уменьшилось в три раза, а новым разработчикам требовалось не две недели, а два дня, чтобы начать продуктивную работу. Самое удивительное, что мы не переписывали логику — мы просто правильно организовали существующий код.
Ключевые преимущества инкапсуляции в JavaScript:
- Сокрытие сложности: пользователи компонента работают с абстракцией, а не с деталями реализации
- Локализация изменений: модификации затрагивают только определенную часть системы, не вызывая каскадных эффектов
- Предсказуемое поведение: четкие интерфейсы гарантируют согласованность работы компонентов
- Защита от некорректного использования: контролируемый доступ предотвращает неправильные манипуляции с данными
- Повышение тестируемости: изолированные компоненты проще тестировать, мокать и заменять
| Критерий | Код без инкапсуляции | Код с инкапсуляцией |
|---|---|---|
| Поддерживаемость | Низкая — изменения требуют анализа всего кодового потока | Высокая — изменения локализованы в рамках компонента |
| Повторное использование | Затруднено из-за сильной связанности | Упрощено благодаря четким интерфейсам |
| Тестируемость | Сложная — требуются сложные сетапы и много моков | Простая — компоненты можно тестировать изолированно |
| Отладка | Трудоемкая — проблемы могут проявляться далеко от источника | Упрощенная — ошибки локализованы внутри компонентов |
JavaScript, будучи мультипарадигмальным языком, предлагает различные механизмы для реализации инкапсуляции, которые мы рассмотрим в следующем разделе. Важно понимать, что инкапсуляция — это не просто технический прием, а философия проектирования, которая меняет подход к структурированию кода. 🔒

Техники инкапсуляции данных в JavaScript
JavaScript предоставляет несколько механизмов для реализации инкапсуляции, каждый со своими особенностями и областью применения. Рассмотрим основные техники и их практическое применение.
Замыкания (Closures)
Замыкания — мощный механизм, позволяющий создавать приватные переменные, доступные только внутри функции:
function createCounter() {
// Приватная переменная
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
getValue() { return count; }
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 1
console.log(counter.count); // undefined – переменная недоступна извне
Замыкания элегантно решают проблему приватных данных, создавая изолированную область видимости и работают во всех версиях JavaScript.
Модули IIFE (Immediately Invoked Function Expressions)
Шаблон модуля на основе немедленно вызываемых функций — классический подход к инкапсуляции в JavaScript:
const calculator = (function() {
// Приватные переменные и функции
let result = 0;
function validateNumber(num) {
return typeof num === 'number' && !isNaN(num);
}
// Публичный интерфейс
return {
add(num) {
if (validateNumber(num)) result += num;
return this; // Для цепочки вызовов
},
subtract(num) {
if (validateNumber(num)) result -= num;
return this;
},
getResult() {
return result;
}
};
})();
calculator.add(5).subtract(2);
console.log(calculator.getResult()); // 3
IIFE создает изолированную область видимости, где можно хранить приватные данные и функции, экспортируя только публичный API.
Классы ES6+ и приватные поля
Современный JavaScript (ECMAScript 2015+) предлагает синтаксис классов и приватные поля (начиная с ES2022):
class BankAccount {
// Приватное поле (поддерживается в современных браузерах)
#balance = 0;
constructor(initialBalance) {
if (initialBalance > 0) {
this.#balance = initialBalance;
}
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
withdraw(amount) {
if (amount > 0 && this.#balance >= amount) {
this.#balance -= amount;
return true;
}
return false;
}
getBalance() {
// Возвращаем копию для предотвращения изменений
return this.#balance;
}
}
const account = new BankAccount(1000);
account.withdraw(500);
console.log(account.getBalance()); // 500
console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
Приватные поля с префиксом # обеспечивают настоящую инкапсуляцию на уровне языка, предотвращая доступ извне.
Символы (Symbols)
Символы — примитивный тип данных, позволяющий создавать "полуприватные" свойства:
const _temperature = Symbol('temperature');
const _pressure = Symbol('pressure');
class WeatherStation {
constructor(temperature, pressure) {
this[_temperature] = temperature;
this[_pressure] = pressure;
}
getWeatherData() {
return {
temperature: this[_temperature],
pressure: this[_pressure]
};
}
}
const station = new WeatherStation(22, 760);
console.log(station.getWeatherData()); // { temperature: 22, pressure: 760 }
console.log(station[_temperature]); // Undefined если _temperature не доступен
Хотя символы не обеспечивают абсолютную приватность (их можно получить через Object.getOwnPropertySymbols), они делают свойства невидимыми при стандартных операциях.
| Техника инкапсуляции | Совместимость | Уровень защиты | Производительность | Удобство использования |
|---|---|---|---|---|
| Замыкания | Все версии JavaScript | Высокий | Средняя (создает дополнительный контекст) | Среднее (многословный синтаксис) |
| Модули IIFE | Все версии JavaScript | Высокий | Средняя | Низкое (требует дополнительного оборачивания) |
| Приватные поля (#) | Современные браузеры, Node.js 12+ | Очень высокий | Высокая (нативная реализация) | Высокое (интуитивный синтаксис) |
| Символы | ES6+ (IE не поддерживает) | Средний | Высокая | Среднее |
| WeakMap | ES6+ (IE не поддерживает) | Высокий | Средняя | Низкое (неочевидный синтаксис) |
Выбор техники инкапсуляции зависит от контекста: требований к поддержке браузеров, необходимого уровня защиты данных и стиля кодирования проекта. В современных проектах рекомендуется использовать приватные поля классов, но замыкания и IIFE остаются надежными альтернативами для обратной совместимости. 🔐
Модульная архитектура: организация кода в JS-проектах
Модульная архитектура — естественное продолжение инкапсуляции на уровне организации проекта. Модули позволяют разбивать сложные системы на независимые, слабосвязанные компоненты с четкими границами ответственности. 📦
Хорошо спроектированная модульная система обладает следующими характеристиками:
- Высокая когезия — каждый модуль выполняет конкретную, хорошо определенную задачу
- Низкая связность — модули минимально зависят друг от друга, взаимодействуя через стабильные интерфейсы
- Предсказуемые зависимости — явное объявление импортов и экспортов
- Инкапсулированная реализация — внутренние детали скрыты от внешнего мира
- Удобство тестирования — возможность изолированно тестировать каждый модуль
Рассмотрим принципы организации модульной архитектуры в JavaScript-проектах.
Принципы проектирования модулей
Принцип единственной ответственности (SRP): каждый модуль должен отвечать за одну конкретную функциональность. Например, отдельные модули для работы с API, управления состоянием, валидации форм и т.д.
// userApi.js – отвечает только за взаимодействие с API пользователей
export async function fetchUsers() {
return await fetch('/api/users').then(res => res.json());
}
export async function updateUserProfile(userId, data) {
return await fetch(`/api/users/${userId}`, {
method: 'PUT',
body: JSON.stringify(data)
}).then(res => res.json());
}
// userValidation.js – отвечает только за валидацию данных пользователя
export function validateUserData(userData) {
const errors = {};
if (!userData.name || userData.name.length < 2) {
errors.name = 'Имя должно содержать не менее 2 символов';
}
if (!userData.email || !/\S+@\S+\.\S+/.test(userData.email)) {
errors.email = 'Укажите корректный email';
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
Принцип открытости/закрытости (OCP): модули должны быть открыты для расширения, но закрыты для модификации. Это достигается через предоставление расширяемых интерфейсов и использование паттернов вроде Strategy или Observer.
// validators.js – набор стратегий валидации
export const validators = {
required: value => !!value || 'Обязательное поле',
email: value => /\S+@\S+\.\S+/.test(value) || 'Некорректный email',
minLength: min => value =>
(!value || value.length >= min) || `Минимум ${min} символов`
};
// Можно расширить, добавив новые валидаторы без изменения существующих
validators.phoneNumber = value =>
/^\+?\d{10,12}$/.test(value) || 'Некорректный номер телефона';
Принцип разделения интерфейса (ISP): лучше предоставлять несколько специализированных интерфейсов, чем один универсальный. Экспортируйте только то, что действительно нужно клиентам модуля.
// Вместо экспорта целого класса с множеством методов
// userManager.js
export function getUserById(id) { /* ... */ }
export function searchUsersByName(name) { /* ... */ }
// Отдельный модуль для административных функций
// userAdmin.js
export function banUser(id) { /* ... */ }
export function promoteToAdmin(id) { /* ... */ }
Марина Соколова, фронтенд-архитектор
Мы столкнулись с типичной проблемой: наш проект рос органически, и разработчики импортировали функции откуда угодно без учета зависимостей. В результате получили циклические зависимости, которые вызывали загадочные ошибки и увеличивали время сборки.
Решение пришло в виде четко определенной модульной архитектуры. Мы разделили проект на слои: ядро, домены, компоненты интерфейса, утилиты. Ввели строгое правило: модуль может зависеть только от модулей своего уровня или нижележащих уровней. Например, домены могут использовать ядро, но не другие домены.
Для валидации архитектуры настроили ESLint с плагином import, который блокировал неправильные импорты. Первые недели были болезненными — приходилось рефакторить сложившиеся паттерны. Но уже через месяц циклические зависимости исчезли, время сборки уменьшилось на 40%, а новые разработчики перестали задавать вопрос: "Откуда мне импортировать X?". Четкая структура сама направляла их к правильным решениям.
Организация файловой структуры
Правильная организация файлов критически важна для модульных проектов. Существует несколько распространенных подходов:
По типу файлов (Type-Based):
/src
/components
/Button
/Form
/Modal
/services
/api
/auth
/analytics
/utils
/formatters
/validators
/store
/user
/products
/cart
По функциональности (Feature-Based):
/src
/features
/auth
/components
/services
/store
/utils
/products
/components
/services
/store
/utils
/checkout
/components
/services
/store
/utils
/common
/components
/utils
Гибридный подход:
/src
/core # Ядро системы, базовая инфраструктура
/api
/config
/store
/features # Функциональные модули
/auth
/products
/checkout
/shared # Общие компоненты и утилиты
/components
/utils
/hooks
/pages # Страницы приложения
/Home
/Profile
/Cart
Стратегии организации зависимостей
Для создания поддерживаемой архитектуры важно правильно организовать зависимости между модулями:
- Явное объявление зависимостей: все зависимости должны быть явно импортированы
- Предотвращение циклических зависимостей: модули не должны образовывать циклы зависимостей
- Инверсия зависимостей: высокоуровневые модули не должны зависеть от деталей реализации низкоуровневых модулей
- Разделение по слоям: организация кода в слои с определенными правилами взаимодействия
Современные инструменты вроде ESLint с плагином import или NDepend для .NET позволяют автоматически проверять соблюдение этих правил.
Модульная архитектура требует дисциплины и постоянной рефлексии, но окупается снижением сложности, улучшением поддерживаемости и возможностью параллельной разработки различных частей проекта. В следующем разделе мы рассмотрим конкретные системы модулей, доступные в современном JavaScript. 🏗️
Современные системы модулей JavaScript на практике
JavaScript прошел долгий путь от отсутствия нативной поддержки модулей до нескольких конкурирующих систем модулей и, наконец, к стандартизированным ES Modules. Рассмотрим ключевые системы модулей и их практическое применение. 🔄
CommonJS (CJS)
CommonJS — система модулей, ставшая стандартом в Node.js. Она использует синхронную загрузку модулей, что подходит для серверных приложений.
// math.js
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
// Экспорт отдельных значений
module.exports = {
PI,
add,
multiply
};
// Альтернативный вариант экспорта
// exports.PI = PI;
// exports.add = add;
// exports.multiply = multiply;
// app.js
const math = require('./math'); // Импорт всего модуля
const { PI, add } = require('./math'); // Деструктуризация при импорте
console.log(math.multiply(2, 3)); // 6
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
Особенности CommonJS:
- Синхронная загрузка модулей
- Динамические импорты через require()
- Кэширование модулей — модуль загружается только один раз
- Не имеет нативной поддержки в браузерах (требует бандлеры вроде Webpack)
- Импорты разрешаются во время выполнения, не на этапе сборки
ES Modules (ESM)
ES Modules — официальный стандарт модулей JavaScript (ECMAScript), поддерживаемый современными браузерами и Node.js.
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// Альтернативный синтаксис
// const PI = 3.14159;
// function add(a, b) { return a + b; }
// function multiply(a, b) { return a * b; }
// export { PI, add, multiply };
// Экспорт по умолчанию
export default {
version: '1.0.0',
author: 'John Doe'
};
// app.js
import { PI, add } from './math.js'; // Именованный импорт
import * as math from './math.js'; // Импорт всего модуля
import metadata from './math.js'; // Импорт по умолчанию
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
console.log(math.multiply(2, 3)); // 6
console.log(metadata.version); // 1.0.0
Особенности ES Modules:
- Статические импорты, анализируемые на этапе сборки
- Поддержка асинхронной загрузки через dynamic import()
- Нативная поддержка в современных браузерах и Node.js
- Tree-shaking — удаление неиспользуемого кода при сборке
- Строгий режим по умолчанию
AMD (Asynchronous Module Definition)
AMD — система модулей, оптимизированная для работы в браузере, где асинхронная загрузка важна для производительности.
// math.js
define(['dependency1', 'dependency2'], function(dep1, dep2) {
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
return {
PI: PI,
add: add,
multiply: multiply
};
});
// app.js
require(['math'], function(math) {
console.log(math.add(2, 3)); // 5
});
AMD сегодня используется значительно реже, но по-прежнему встречается в некоторых устаревших проектах или библиотеках.
UMD (Universal Module Definition)
UMD — гибридный формат, поддерживающий несколько систем модулей (CommonJS, AMD, глобальные переменные).
// umdModule.js
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['dependency'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory(require('dependency'));
} else {
// Browser globals
root.myModule = factory(root.dependency);
}
}(typeof self !== 'undefined' ? self : this, function(dependency) {
return {
// Модуль
doSomething: function() {
return 'Сделано!';
}
};
}));
UMD часто используется для библиотек, которые должны работать в разных окружениях.
| Система модулей | Загрузка | Синтаксис | Поддержка в браузерах | Применение |
|---|---|---|---|---|
| CommonJS | Синхронная | require() / module.exports | Только через бандлеры | Node.js, серверные приложения |
| ES Modules | Статическая / Асинхронная | import / export | Нативная в современных браузерах | Современный JS, фронтенд и бэкенд |
| AMD | Асинхронная | define() / require() | Через RequireJS или аналоги | Устаревшие фронтенд приложения |
| UMD | Гибридная | Комбинированный паттерн | Через обертку или бандлеры | Универсальные библиотеки |
Практические рекомендации по использованию модулей
На основе опыта работы с различными проектами, можно дать следующие рекомендации:
- Используйте ES Modules по умолчанию для новых проектов — это стандарт с наилучшей поддержкой инструментов
- Соблюдайте принцип единственной ответственности: один модуль должен делать одну вещь хорошо
- Избегайте создания монолитных модулей с десятками экспортов — разделяйте их на логические группы
- Используйте barrel files (index.js) для упрощения импортов из сложных структур
- Применяйте линтеры (например, eslint-plugin-import) для проверки структуры импортов
- Внедряйте tree-shaking через корректные экспорты для уменьшения размера бандла
// barrel file (index.js)
export { default as Button } from './Button';
export { default as Input } from './Input';
export { default as Select } from './Select';
// usage
import { Button, Input } from './components';
// Вместо
// import Button from './components/Button';
// import Input from './components/Input';
При переходе с CommonJS на ES Modules в Node.js проектах обратите внимание на следующие ключевые отличия:
- ES Modules работают в строгом режиме по умолчанию
- Переменные this, dirname, filename работают иначе или недоступны
- Импорты должны иметь расширение файла (.js, .mjs)
- JSON-файлы требуют специального импорта через import()
Выбор системы модулей должен основываться на требованиях проекта, целевой среде выполнения и предпочтениях команды. В 2023 году ES Modules — очевидный выбор для новых проектов, однако понимание других систем важно для работы с существующим кодом. 📦
Шаблоны проектирования для эффективной инкапсуляции
Шаблоны проектирования — проверенные решения типичных проблем в разработке программного обеспечения. Рассмотрим шаблоны, которые особенно полезны для эффективной инкапсуляции в JavaScript. 🧩
Модуль (Module)
Шаблон Модуль — основной способ организации кода с инкапсуляцией. Он создает закрытую область видимости и возвращает публичный API:
// Модуль для работы с хранилищем
const StorageModule = (function() {
// Приватные переменные и функции
const prefix = 'app_';
function getKeyWithPrefix(key) {
return `${prefix}${key}`;
}
function isSupported() {
try {
return 'localStorage' in window && window.localStorage !== null;
} catch (e) {
return false;
}
}
// Публичный API
return {
set(key, value) {
if (!isSupported()) return false;
try {
localStorage.setItem(
getKeyWithPrefix(key),
JSON.stringify(value)
);
return true;
} catch (e) {
console.error('Storage error:', e);
return false;
}
},
get(key, defaultValue = null) {
if (!isSupported()) return defaultValue;
try {
const value = localStorage.getItem(getKeyWithPrefix(key));
return value ? JSON.parse(value) : defaultValue;
} catch (e) {
console.error('Storage error:', e);
return defaultValue;
}
},
remove(key) {
if (!isSupported()) return false;
try {
localStorage.removeItem(getKeyWithPrefix(key));
return true;
} catch (e) {
console.error('Storage error:', e);
return false;
}
},
clear() {
if (!isSupported()) return false;
try {
// Удаляем только элементы с нашим префиксом
Object.keys(localStorage)
.filter(key => key.startsWith(prefix))
.forEach(key => localStorage.removeItem(key));
return true;
} catch (e) {
console.error('Storage error:', e);
return false;
}
}
};
})();
// Использование
StorageModule.set('user', { id: 1, name: 'John' });
const user = StorageModule.get('user');
Фабрика (Factory)
Шаблон Фабрика создает объекты без необходимости знать их конкретные классы, что способствует инкапсуляции деталей создания:
// Фабрика HTTP-клиентов
const HTTPClientFactory = {
createJSONClient(baseURL) {
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
return {
async get(url, params = {}) {
const queryString = Object.entries(params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
const fullURL = `${baseURL}${url}${queryString ? `?${queryString}` : ''}`;
const response = await fetch(fullURL, { headers });
return response.json();
},
async post(url, data) {
const response = await fetch(`${baseURL}${url}`, {
method: 'POST',
headers,
body: JSON.stringify(data)
});
return response.json();
}
// Другие методы...
};
},
createFormClient(baseURL) {
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
return {
// Реализация методов для работы с формами...
};
}
};
// Использование
const apiClient = HTTPClientFactory.createJSONClient('https://api.example.com');
apiClient.get('/users/1').then(user => console.log(user));
Фасад (Facade)
Фасад предоставляет унифицированный интерфейс к набору интерфейсов подсистемы, скрывая сложность:
// Фасад для работы с API социальной сети
class SocialMediaFacade {
constructor(apiKey) {
// Инициализация сложных подсистем
this.userAPI = new UserAPIClient(apiKey);
this.postAPI = new PostAPIClient(apiKey);
this.mediaAPI = new MediaAPIClient(apiKey);
this.analyticsAPI = new AnalyticsAPIClient(apiKey);
}
// Простой унифицированный интерфейс
async sharePost(userId, content, mediaIds = []) {
try {
// Проверка прав пользователя
const user = await this.userAPI.getUserById(userId);
if (!user.canPost) {
throw new Error('User does not have permission to post');
}
// Проверка и подготовка медиа
const mediaItems = mediaIds.length
? await this.mediaAPI.getMediaItems(mediaIds)
: [];
// Создание поста с обработкой ошибок и повторными попытками
const post = await this.postAPI.createPost({
userId,
content,
mediaItems: mediaItems.map(m => m.id)
});
// Логирование для аналитики
this.analyticsAPI.logUserAction(userId, 'post_created', {
postId: post.id,
hasMedia: mediaItems.length > 0
});
return {
success: true,
postId: post.id,
url: `https://social.example.com/posts/${post.id}`
};
} catch (error) {
console.error('Failed to share post:', error);
return {
success: false,
error: error.message
};
}
}
// Другие простые методы для сложных операций...
}
// Использование
const socialMedia = new SocialMediaFacade('api_key_123');
socialMedia.sharePost(123, 'Hello, world!').then(result => {
if (result.success) {
console.log(`Post shared: ${result.url}`);
} else {
console.error(`Failed to share post: ${result.error}`);
}
});
Команда (Command)
Шаблон Команда инкапсулирует запрос как объект, позволяя параметризовать клиенты с различными запросами:
// Команды для редактора текста
class TextEditor {
constructor(text = '') {
this.text = text;
this.history = [];
}
executeCommand(command) {
this.history.push(command);
command.execute(this);
}
undo() {
if (this.history.length > 0) {
const command = this.history.pop();
command.undo(this);
}
}
getText() {
return this.text;
}
setText(text) {
this.text = text;
}
}
// Базовый класс команды
class EditorCommand {
execute() {
throw new Error('Method not implemented');
}
undo() {
throw new Error('Method not implemented');
}
}
// Конкретные команды
class InsertTextCommand extends EditorCommand {
constructor(position, text) {
super();
this.position = position;
this.text = text;
}
execute(editor) {
const currentText = editor.getText();
const newText =
currentText.slice(0, this.position) +
this.text +
currentText.slice(this.position);
editor.setText(newText);
}
undo(editor) {
const currentText = editor.getText();
const newText =
currentText.slice(0, this.position) +
currentText.slice(this.position + this.text.length);
editor.setText(newText);
}
}
class DeleteTextCommand extends EditorCommand {
constructor(position, length) {
super();
this.position = position;
this.length = length;
this.deletedText = '';
}
execute(editor) {
const currentText = editor.getText();
this.deletedText = currentText.slice(this.position, this.position + this.length);
const newText =
currentText.slice(0, this.position) +
currentText.slice(this.position + this.length);
editor.setText(newText);
}
undo(editor) {
const currentText = editor.getText();
const newText =
currentText.slice(0, this.position) +
this.deletedText +
currentText.slice(this.position);
editor.setText(newText);
}
}
// Использование
const editor = new TextEditor('Hello');
editor.executeCommand(new InsertTextCommand(5, ', world'));
console.log(editor.getText()); // "Hello, world"
editor.executeCommand(new DeleteTextCommand(5, 7));
console.log(editor.getText()); // "Hello"
editor.undo();
console.log(editor.getText()); // "Hello, world"
Стратегия (Strategy)
Шаблон Стратегия определяет семейство алгоритмов, инкапсулируя каждый из них и делая взаимозаменяемыми:
// Стратегии валидации
class ValidationContext {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
validate(data) {
return this.strategy.validate(data);
}
}
// Интерфейс стратегии
class ValidationStrategy {
validate(data) {
throw new Error('Method not implemented');
}
}
// Конкретные стратегии
class EmailValidator extends ValidationStrategy {
validate(email) {
const regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
return {
isValid: regex.test(email),
message: regex.test(email) ? '' : 'Некорректный email адрес'
};
}
}
class PasswordValidator extends ValidationStrategy {
constructor(minLength = 8) {
super();
this.minLength = minLength;
}
validate(password) {
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasDigit = /[0-9]/.test(password);
const hasMinLength = password.length >= this.minLength;
const isValid = hasUpperCase && hasLowerCase && hasDigit && hasMinLength;
let message = '';
if (!isValid) {
message = 'Пароль должен содержать:';
if (!hasMinLength) message += `\n- не менее ${this.minLength} символов`;
if (!hasUpperCase) message += '\n- хотя бы одну заглавную букву';
if (!hasLowerCase) message += '\n- хотя бы одну строчную букву';
if (!hasDigit) message += '\n- хотя бы одну цифру';
}
return { isValid, message };
}
}
// Использование
const validator = new ValidationContext(new EmailValidator());
console.log(validator.validate('user@example.com')); // { isValid: true, message: '' }
console.log(validator.validate('invalid-email')); // { isValid: false, message: 'Некорректный email адрес' }
validator.setStrategy(new PasswordValidator(10));
console.log(validator.validate('Passw0rd123')); // { isValid: true, message: '' }
console.log(validator.validate('weak')); // { isValid: false, message: '...' }
Эти шаблоны проектирования предоставляют структурные решения для достижения эффективной инкапсуляции, делая код более модульным, гибким и удобным для сопровождения. Они действуют как строительные блоки для создания масштабируемых и поддерживаемых приложений, позволяя реализовать сложную логику без увеличения сложности всей системы. 🏗️
Овладев принципами инкапсуляции и модульности, вы обретаете новый уровень контроля над кодовой базой. Представьте, что каждый компонент вашей системы — самодостаточный микромир с четкими границами и контрактами взаимодействия. Такая архитектура противостоит энтропии программного обеспечения, когда код превращается в запутанный клубок зависимостей. Возьмите эти инструменты и применяйте их осознанно: разделяйте код на модули с единственной ответственностью, скрывайте детали реализации за чистыми интерфейсами, используйте современные системы модулей и проверенные шаблоны проектирования. Результат — не просто код, который работает сегодня, а архитектура, которая будет служить вам и вашей команде годами.
Читайте также
- Промисы в JavaScript: полное руководство по асинхронной работе
- Взаимодействие с формами в JavaScript: обработка, валидация, API
- Поиск и манипуляция элементами DOM: методы JavaScript для разработчиков
- Установка и настройка среды разработки для JavaScript: пошаговое руководство
- Поиск и сортировка массивов в JavaScript: методы и алгоритмы
- Разработка одностраничных приложений на JavaScript: полное руководство
- Создание сервера на Node.js: пошаговое руководство для новичков
- Многомерные массивы в JavaScript: создание, обработка, примеры
- Введение в React: пошаговое руководство для начинающих разработчиков
- Отладка и тестирование JavaScript кода: инструменты и методы
Владимир Титов
редактор про сервисные сферы