Как можно использовать инкапсуляцию?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Инкапсуляция в JavaScript и Node.js
Инкапсуляция — это один из основных принципов объектно-ориентированного программирования, который скрывает внутренние детали реализации и предоставляет контролируемый доступ к данным через публичный интерфейс. В JavaScript это можно реализовать несколькими способами.
1. Приватные поля в классах (ES2022)
Современный и рекомендуемый подход с использованием префикса #:
class BankAccount {
#balance = 0; // приватное поле
#pin = null; // приватное поле
constructor(initialBalance, pin) {
this.#balance = initialBalance;
this.#pin = pin;
}
// Публичный метод
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#logTransaction('deposit', amount);
return true;
}
return false;
}
// Публичный метод
withdraw(amount, pin) {
if (this.#verifyPin(pin) && amount <= this.#balance) {
this.#balance -= amount;
this.#logTransaction('withdraw', amount);
return true;
}
return false;
}
// Публичный метод для чтения баланса
getBalance(pin) {
if (this.#verifyPin(pin)) {
return this.#balance;
}
throw new Error('Invalid PIN');
}
// Приватный метод
#verifyPin(pin) {
return this.#pin === pin;
}
// Приватный метод
#logTransaction(type, amount) {
console.log(`[${new Date().toISOString()}] ${type}: ${amount}`);
}
}
// Использование
const account = new BankAccount(1000, '1234');
console.log(account.deposit(500)); // true
console.log(account.withdraw(200, '1234')); // true
console.log(account.getBalance('1234')); // 1300
// Ошибка: доступ к приватному полю невозможен
console.log(account.#balance); // SyntaxError
2. Замыкания (Closures)
Классический подход, работает во всех версиях JavaScript:
function createCounter() {
let count = 0; // приватная переменная в замыкании
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// Нельзя получить count напрямую
console.log(counter.count); // undefined
Этот паттерн часто используется в Node.js модулях:
// db.js
const connection = require('some-db');
const dbInstance = null; // приватная переменная
module.exports = {
connect(config) {
dbInstance = connection.create(config);
return dbInstance;
},
query(sql) {
if (!dbInstance) {
throw new Error('Database not connected');
}
return dbInstance.query(sql);
},
disconnect() {
if (dbInstance) {
dbInstance.close();
dbInstance = null;
}
}
};
3. WeakMap для приватных данных
Элегантный способ скрыть данные для экземпляров класса:
const privateData = new WeakMap();
class User {
constructor(username, password) {
this.username = username; // публичное
// Приватные данные хранятся в WeakMap
privateData.set(this, {
password: password,
tokens: [],
lastLogin: null
});
}
authenticate(password) {
const data = privateData.get(this);
return data.password === password;
}
addToken(token) {
const data = privateData.get(this);
data.tokens.push(token);
}
getTokens() {
const data = privateData.get(this);
return [...data.tokens]; // возвращаем копию
}
}
const user = new User('john', 'secret123');
console.log(user.authenticate('secret123')); // true
console.log(user.username); // john
console.log(privateData.get(user).password); // secret123
4. Getters и Setters для контроля доступа
Дают возможность добавить валидацию при доступе к свойствам:
class Temperature {
#celsius = 0;
// Getter
get celsius() {
return this.#celsius;
}
// Setter с валидацией
set celsius(value) {
if (typeof value !== 'number') {
throw new TypeError('Temperature must be a number');
}
if (value < -273.15) {
throw new RangeError('Temperature cannot be below absolute zero');
}
this.#celsius = value;
}
get fahrenheit() {
return (this.#celsius * 9/5) + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) * 5/9;
}
}
const temp = new Temperature();
temp.celsius = 25; // использует setter
console.log(temp.celsius); // 25 (getter)
console.log(temp.fahrenheit); // 77
// Валидация работает
temp.celsius = -300; // RangeError: Temperature cannot be below absolute zero
5. Абстрактные классы с приватными методами
Для создания правил наследования:
class Database {
#connection = null;
// Публичный метод
async connect(config) {
await this.#validateConfig(config);
this.#connection = await this.#createConnection(config);
}
// Публичный метод
async query(sql, params = []) {
if (!this.#connection) {
throw new Error('Database not connected');
}
return this.#executeQuery(sql, params);
}
// Приватные методы
async #validateConfig(config) {
if (!config.host || !config.port) {
throw new Error('Invalid config');
}
}
async #createConnection(config) {
// имплементация
}
async #executeQuery(sql, params) {
// имплементация
}
}
class PostgreSQL extends Database {
// может наследовать public методы, не видит приватные
}
6. Интерфейсы в TypeScript
Для строгой инкапсуляции с типизацией:
interface IUser {
getEmail(): string;
authenticate(password: string): boolean;
}
class User implements IUser {
private email: string;
private passwordHash: string;
private tokens: string[] = [];
constructor(email: string, passwordHash: string) {
this.email = email;
this.passwordHash = passwordHash;
}
public getEmail(): string {
return this.email;
}
public authenticate(password: string): boolean {
return this.validatePassword(password);
}
private validatePassword(password: string): boolean {
// валидация с хешированием
return true;
}
}
const user: IUser = new User('john@example.com', 'hash123');
console.log(user.getEmail()); // john@example.com
// user.email не доступен (TypeScript ошибка)
7. Принцип инкапсуляции в архитектуре
На уровне модулей и сервисов:
// userService.js - публичный интерфейс
const userRepository = require('./userRepository');
const emailService = require('./emailService');
const passwordUtils = require('./passwordUtils'); // приватные утилиты
module.exports = {
async register(email, password) {
// публичный метод
const hashedPassword = await passwordUtils.hash(password);
return userRepository.create(email, hashedPassword);
},
async authenticate(email, password) {
const user = await userRepository.findByEmail(email);
if (!user) return null;
const isValid = await passwordUtils.compare(password, user.passwordHash);
if (isValid) {
return { id: user.id, email: user.email };
}
return null;
}
// приватная функция (не экспортируется)
};
// app.js - использует только публичный интерфейс
const userService = require('./userService');
await userService.register('user@example.com', 'password123');
Сравнение подходов
| Подход | Поддержка | Производительность | Использование |
|---|---|---|---|
| # приватные поля | ES2022+ | Быстро | Рекомендуется |
| Замыкания | Все версии | Хорошо | Модули, IIFE |
| WeakMap | ES6+ | Нормально | Специфичные случаи |
| Getters/Setters | ES5+ | Хорошо | Валидация доступа |
| TypeScript private | TypeScript | Зависит | Строгая типизация |
Лучшие практики инкапсуляции
- Минимизируйте публичный интерфейс — экспортируйте только необходимое
- Используйте приватные поля — для новых проектов с ES2022+
- Добавляйте валидацию — через getters и setters
- Документируйте API — что публичное, что приватное
- Избегайте нарушения инкапсуляции — не обращайтесь к внутренним деталям
// Хороший пример
class API {
#apiKey = null;
constructor(apiKey) {
this.#apiKey = apiKey;
}
async request(endpoint, method = 'GET') {
return this.#makeRequest(endpoint, method);
}
#makeRequest(endpoint, method) {
// имплементация с использованием приватного apiKey
}
}
Заключение
Инкапсуляция в JavaScript реализуется разными способами в зависимости от версии языка и требований проекта. Современный подход использует приватные поля с #, старые проекты полагаются на замыкания. Правильная инкапсуляция делает код безопаснее, поддерживаемее и предсказуемее.