Инкапсуляция и модули в JavaScript: принципы структурирования кода
Перейти

Инкапсуляция и модули в JavaScript: принципы структурирования кода

#Разное  
Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Разработчики, работающие с JavaScript, желающие улучшить качество и структуру своего кода
  • Технические архитекторы и лидеры разработки, заинтересованные в внедрении принципов инкапсуляции и модульности
  • Студенты и профессионалы, углубляющие знания об ООП в JavaScript и современных практиках программирования

JavaScript эволюционировал из простого языка для валидации форм в мощный инструмент построения сложных приложений, где структурированный код — не роскошь, а необходимость. Разработчики, погружающиеся в хаос неорганизованного кода, теряют до 42% продуктивного времени на понимание и рефакторинг запутанной логики. Инкапсуляция и модульность — это не просто теоретические концепции из учебников по ООП, а практические подходы, позволяющие создавать масштабируемые решения, где код можно не только писать, но и поддерживать годами. Готовы превратить спагетти-код в элегантную архитектуру? 🚀

Инкапсуляция в JavaScript: концепция и преимущества

Инкапсуляция — один из фундаментальных принципов ООП, который позволяет ограничить доступ к внутренним механизмам объекта, предоставляя интерфейс для взаимодействия с ним. В JavaScript, языке с динамической типизацией и прототипным наследованием, инкапсуляция приобретает особое значение.

Представьте инкапсуляцию как черный ящик: пользователи знают, какие кнопки нажимать для получения результата, но внутренние механизмы скрыты. Это не просто сокрытие данных, а целостная концепция проектирования, где компоненты системы имеют четкие границы и интерфейсы взаимодействия.

Алексей Ветров, технический архитектор

Однажды мы унаследовали проект с 15000 строк монолитного кода в одном файле main.js. Каждое изменение превращалось в детективное расследование, а побочные эффекты всплывали в самых неожиданных местах. Мы потратили два месяца на рефакторинг с применением принципов инкапсуляции: выделили независимые модули, определили четкие интерфейсы взаимодействия, скрыли внутренние детали реализации.

Результат превзошел ожидания: время на внедрение новой функциональности сократилось на 68%, количество регрессионных багов уменьшилось в три раза, а новым разработчикам требовалось не две недели, а два дня, чтобы начать продуктивную работу. Самое удивительное, что мы не переписывали логику — мы просто правильно организовали существующий код.

Ключевые преимущества инкапсуляции в JavaScript:

  • Сокрытие сложности: пользователи компонента работают с абстракцией, а не с деталями реализации
  • Локализация изменений: модификации затрагивают только определенную часть системы, не вызывая каскадных эффектов
  • Предсказуемое поведение: четкие интерфейсы гарантируют согласованность работы компонентов
  • Защита от некорректного использования: контролируемый доступ предотвращает неправильные манипуляции с данными
  • Повышение тестируемости: изолированные компоненты проще тестировать, мокать и заменять
Критерий Код без инкапсуляции Код с инкапсуляцией
Поддерживаемость Низкая — изменения требуют анализа всего кодового потока Высокая — изменения локализованы в рамках компонента
Повторное использование Затруднено из-за сильной связанности Упрощено благодаря четким интерфейсам
Тестируемость Сложная — требуются сложные сетапы и много моков Простая — компоненты можно тестировать изолированно
Отладка Трудоемкая — проблемы могут проявляться далеко от источника Упрощенная — ошибки локализованы внутри компонентов

JavaScript, будучи мультипарадигмальным языком, предлагает различные механизмы для реализации инкапсуляции, которые мы рассмотрим в следующем разделе. Важно понимать, что инкапсуляция — это не просто технический прием, а философия проектирования, которая меняет подход к структурированию кода. 🔒

Пошаговый план для смены профессии

Техники инкапсуляции данных в JavaScript

JavaScript предоставляет несколько механизмов для реализации инкапсуляции, каждый со своими особенностями и областью применения. Рассмотрим основные техники и их практическое применение.

Замыкания (Closures)

Замыкания — мощный механизм, позволяющий создавать приватные переменные, доступные только внутри функции:

JS
Скопировать код
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:

JS
Скопировать код
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):

JS
Скопировать код
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)

Символы — примитивный тип данных, позволяющий создавать "полуприватные" свойства:

JS
Скопировать код
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, управления состоянием, валидации форм и т.д.

JS
Скопировать код
// 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.

JS
Скопировать код
// 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): лучше предоставлять несколько специализированных интерфейсов, чем один универсальный. Экспортируйте только то, что действительно нужно клиентам модуля.

JS
Скопировать код
// Вместо экспорта целого класса с множеством методов
// 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):

plaintext
Скопировать код
/src
/components
/Button
/Form
/Modal
/services
/api
/auth
/analytics
/utils
/formatters
/validators
/store
/user
/products
/cart

По функциональности (Feature-Based):

plaintext
Скопировать код
/src
/features
/auth
/components
/services
/store
/utils
/products
/components
/services
/store
/utils
/checkout
/components
/services
/store
/utils
/common
/components
/utils

Гибридный подход:

plaintext
Скопировать код
/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. Она использует синхронную загрузку модулей, что подходит для серверных приложений.

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.

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 — система модулей, оптимизированная для работы в браузере, где асинхронная загрузка важна для производительности.

JS
Скопировать код
// 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, глобальные переменные).

JS
Скопировать код
// 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 через корректные экспорты для уменьшения размера бандла
JS
Скопировать код
// 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:

JS
Скопировать код
// Модуль для работы с хранилищем
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)

Шаблон Фабрика создает объекты без необходимости знать их конкретные классы, что способствует инкапсуляции деталей создания:

JS
Скопировать код
// Фабрика 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)

Фасад предоставляет унифицированный интерфейс к набору интерфейсов подсистемы, скрывая сложность:

JS
Скопировать код
// Фасад для работы с 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)

Шаблон Команда инкапсулирует запрос как объект, позволяя параметризовать клиенты с различными запросами:

JS
Скопировать код
// Команды для редактора текста
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)

Шаблон Стратегия определяет семейство алгоритмов, инкапсулируя каждый из них и делая взаимозаменяемыми:

JS
Скопировать код
// Стратегии валидации
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?
1 / 5

Владимир Титов

редактор про сервисные сферы

Свежие материалы

Загрузка...